I have two tables in database orders and offers. Earlier there was #OneToOne mapping between two i.e. for a single order, there was a single offer. Corresponding domains are:
#Entity
#Table(name = "orders")
#DiscriminatorFormula("0")
#DiscriminatorValue("0")
class Order {
#OneToOne(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Offer offer;
public Offer getOffer() {
return this.offer;
}
public void setOffer(Offer offer) {
this.offer = offer;
}
}
#Entity
#Table(name = "offers")
class Offer {
}
Now, i want OneToMany mapping between two i.e. for a single order, there can be multiple offers now. But for that, i want to build new version of Domain so as not to effect existing functionality. As it is OneToMany mapping so i will have to use Set or List. So, effectively, i want did:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offer;
public Set<Offer> getOffer() {
return this.offer;
}
public void setOffer(Set<Offer> offer) {
this.offer = offer;
}
}
How can i achieve this as currently it is giving me error in getter method as overridden method cannot have different return type.
Actually your problem is that you are using a field with the same name offer as the field in the super class while both have different types, so it will be confusing because you will have the child getter for Set<Offer> overriding the parent getter for Offer, that's why you get the Exception:
error in getter method as overridden method cannot have different return type
What you will have to do here is to use a different name for the field in your child class, for example offers, so the Model will be correct and Hibernate will correctly map the objects:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order1", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offers;
public Set<Offer> getOffers() {
return this.offers;
}
public void setOffers(Set<Offer> offers) {
this.offers = offers;
}
}
Note:
You need to have two objects of type Order in your Offer class, one for the mapping of offer and the second for the offers mapping, notice the mappedBy = "order1" in the mapping.
Related
Lets say I have an entity like this,
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
FetchType.LAZY
)
#JsonManagedReference
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
}
I want clone this entity, modified a few fields and then persist the new cloned entity in the database. What's the best approach to achieve this?
You can use a copy using constructor or the builder pattern, or to set the properties using simple setters. Then, persist the new entity instance using the EntityManager's persist() method. To avoid issues with duplicated id values, you must not copy the id field and instead let the JPA generate a new id when the entity is persisted.
Code example
public class Post {
// constructor, getters and setters are omitted
public Post(Post post) {
//Don’t copy id!
this.title = post.getTitle();
this.comments = new ArrayList<>(post.getComments());
this.details = new PostDetails(post.getDetails());
}
}
public class PostDetails {
// constructor, getters and setters are omitted
public PostDetails(PostDetails details) {
// Don’t copy id!
this.description = details.getDescription();
// copy other fields as needed
}
}
// Usage
public class PostsRepository{
#Autowired
EntityManager em;
public void saveCopy(Post originalPost){
Post clonedPost = new Post(originalPost);
em.persist(clonedPost);
}
}
Also I would highly recommend you to write tests in order to check that you are doing everything fine. Moreover you will more space for experiments.
I have the following entities:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne
private User user;
...
}
#Entity
#Table(name = "user_cars")
public class UserCar {
...
private Integer userId;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "userId", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
As you can see, userCars are loaded lazily (and I am not going to change it). And now I use Specifications in order to fetch UserData:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
if (Objects.nonNull(userId)) {
final Join<UserData, User> joinParent = root.join("user");
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
But I have no idea how to add there a fetch join clause in order to fetch user cars. I tried to add it in different place and I got either LazyInitializationException (so it didn't work) or some other exceptions...
Slightly different approach from the prior answer, but I think the idea jcc mentioned is on point, i.e. "Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship."
To that end, I'm wondering if the Object-Relational engine is getting confused because you have linked directly to a userId (a primitive) instead of a User (the entity). I'm not sure if it can assume that "userId" the primitive necessarily implies a connection to the User entity.
Can you try to re-arrange the mapping so that it's not using an integer UserId in the join table and instead using the object itself, and then see if it allows the entity manager to understand your query better?
So the mapping might look something like this:
#Entity
#Table(name = "user_cars")
public class UserCar {
...
#ManyToOne
#JoinColumn(name="user_id", nullable=false) // Assuming it's called user_id in this table
private User user;
...
}
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<UserCar> userCars;
...
}
It would also be more in line with
https://www.baeldung.com/hibernate-one-to-many
In addition of the suggestion #crizzis provided in the question comments, please, try to join fetch the user relationship as well; in the error you reported:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
Hibernate is complaining because it it unable to find the owner, user in this case, of the userCars relationship.
It is strange in a certain way because the #ManyToOne relationship will fetch eagerly the user entity and it will be projected as well while obtaining userData but probably Hibernate is performing the query analysis prior to the actual fetch phase. It would be great if somebody could provide some additional insight about this point.
Having said that, please, consider to set the fetch strategy explicitly to FetchType.LAZY in your #ManyToOne relationship:
#Entity
#Table(name = "user_data")
public class UserData {
...
#ManyToOne(fetch= FetchType.LAZY)
private User user;
...
}
Your Specification code can look like the following:
public Page<UserData> getUserData(final SpecificationParameters parameters) {
return userDataRepository.findAll(createSpecification(parameters), parameters.pageable);
}
private Specification<UserData> createSpecification(final SpecificationParameters parameters) {
final var clazz = UserData.class;
return Specification.where(buildUser(parameters.getUserId()));
}
private Specification<UserData> buildUser(final Integer userId) {
return (root, criteriaQuery, criteriaBuilder) -> {
// Fetch user and associated userCars
final Join<UserData, User> joinParent = (Join<UserData, User>)root.fetch("user");
joinParent.fetch("userCars");
// Apply filter, when provided
if (Objects.nonNull(userId)) {
return criteriaBuilder.equal(joinParent.get("id"), userId);
} else {
return criteriaBuilder.isTrue(criteriaBuilder.literal(true));
}
};
}
I did not pay attention to the entity relations themself previously, but Atmas give you a good advice indeed, it will be the more performant way to handle the data in that relationship.
At least, it would be appropriate to define the relationship between User and UserCars using a #JoinColumn annotation instead of mapping through a non entity field in order to prevent errors or an incorrect behavior of your entities. Consider for instance:
#Entity
#Table(name = "users")
public class User {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<UserCar> userCars;
...
}
I'm trying to use JPA (with Hibernate) to save 2 entities. Spring data is providing the interface but I don't think it matters here.
I have a main entity called 'Model'. This model has many 'Parameter' entities linked. I'm writing a method to save a model and its parameters.
This is the method:
private void cascadeSave(Model model) {
modelRepository.save(model);
for (ParameterValue value : model.getParameterValues()) {
parameterValueRepository.save(value);
}
}
This is the problem:
When I load a Model that already existed before, add some new parameters to it and then call this method to save both of them something strange happens:
Before the first save (modelRepository.save) this is what the model's data looks like when debugging:
The model has 2 parameters, with filled in values (name and model are filled).
Now, after saving the model the first save in my method, this happens. Note that the object reference is a different one so Hibernate must have done something magical and recreated the values instead of leaving them alone:
For some reason hibernate cleared all the attributes of the parameters in the set.
Now when the saving of the new parameters happens in the following code it fails because of not null constraints etc.
My question: Why does hibernate clear all of the fields?
Here are the relevant mappings:
ParameterValue
#Entity
#Table(name = "tbl_parameter_value")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "PARAMETER_TYPE")
public abstract class ParameterValue extends AbstractBaseObject {
#Column(nullable = false)
#NotBlank
private String name;
private String stringValue;
private Double doubleValue;
private Integer intValue;
private Boolean booleanValue;
#Enumerated(EnumType.STRING)
private ModelType modelParameterType;
#Column(precision = 7, scale = 6)
private BigDecimal bigDecimalValue;
#Lob
private byte[] blobValue;
ParameterValue() {
}
ParameterValue(String name) {
this.name = name;
}
ModelParameterValue
#Entity
#DiscriminatorValue(value = "MODEL")
public class ModelParameterValue extends ParameterValue {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "model_id", foreignKey = #ForeignKey(name = "FK_VALUE_MODEL"))
private Model model;
ModelParameterValue() {
super();
}
ModelParameterValue(String name) {
super(name);
}
Model
#Entity
#Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "model")
private Set<ModelParameterValue> parameterValues = new HashSet<>();
EDIT
I was able to reproduce this with a minimal example.
If you replace everything spring data does this is what happened under the hood (em is a JPA EntityManager):
public Model simpleTest() {
Model model = new Model("My Test Model");
em.persist(model);
model.addParameter(new Parameter("Param 1"));
em.merge(model);
for (Parameter child : model.getParameters()) {
em.persist(child);
}
return model;
}
When the merge is executed, all of the attributes of the parameters are set to null. They are actually just replaced with completely new parameters.
I guess you are using Spring Data Jpa as your modelRepository. This indicates following consequences.
Spring Repository Save
S save(S entity)
Saves a given entity. Use the returned
instance for further operations as the save operation might have
changed the entity instance completely.
So it is normal behaviour which you had encountered.
Code should be changed to :
model = modelRepository.save(model);
for (ParameterValue value : model.getParameterValues()) {
parameterValueRepository.save(value);
}
EDIT:
I think that your saving function is broken in sense, that you do it backwards. Either you can use CascadeType on your relation or you have to save children first.
Cascade
Cascade works like that "If you save Parent, save Children, if you update Parent, update Children ..."
So we can put cascade on your relation like that :
#Entity
#Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "model", cascade = CascadeType.ALL)
private Set<ModelParameterValue> parameterValues = new HashSet<>();
and then only save like this
private void cascadeSave(Model model) {
modelRepository.save(model);
//ParamValues will be saved/updated automaticlly if your model has changed
}
Bottom-Up save
Second option is just to save params first and then model with them.
private void cascadeSave(Model model) {
model.setParameterValues(
model.getParameterValues().stream()
.map(param -> parameterValueRepository.save(param))
.collect(Collectors.toSet())
);
modelRepository.save(model);
}
I haven't checked second code in my compiler but the idea is to first save children (ParamValues), put it into Model and then save Model : )
I have a class, which can be referenced by many different classes in a Many-To-One or One-To-One relationship or have no referencing object. When an element of this class is deleted, the objects pointing to it should be removed too. What is the most beautiful way to achieve this behavior?
class A {
public remove() {
// remove the element which is pointing to me
}
}
class B {
#ManyToOne
private as
}
class C {
#ManyToOne
private as
}
...
First Of All, I don't think it is a 'beautiful' solution to put business methods in your entity class.
I would recommend creating DAO object for your A class and make your relationship bi-directional with CascadeType set to REMOVE:
#Entity
class A {
#OneToMany(mappedBy = "parentB", cascade = CascadeType.REMOVE)
private Set<Child> childrenB;
#OneToMany(mappedBy = "parentC", cascade = CascadeType.REMOVE)
private Set<Child> childrenC;
}
#Stateless
class daoA {
#PersistenceContext
EntityManager em;
public void remove(A a){
em.delete(a);
}
}
Suppose I have self linked Category entity defined as follows:
public class Category {
#Id
#Access(AccessType.FIELD)
public String Url;
#ManyToOne(optional=true)
#Access(AccessType.FIELD)
public Category Parent;
#OneToMany
private Set<Category> subs;
public void addSub(Category sub) {
subs.add(sub);
}
public void removeSub(Category sub) {
subs.remove(sub);
}
#Access(AccessType.FIELD)
public String Title;
#Access(AccessType.FIELD)
public boolean Done;
I wonder, will it work correctly if I create new Category and add it with my addSub method? Will Category be persisted correctly? Will subcategories be persisted automatically and in correct order?
In the current state of your code - no. To make it work as you want you need to do the followng:
Connect sides of bidirectional relationship with mappedBy on #OneToMany, otherwise Hibernate would think that you have two different relationships:
#OneToMany(mappedBy = "Parent")
It's your responsibility to keep both sides of your relationship in consistent state:
public void addSub(Category sub) {
sub.setParent(this);
subs.add(sub);
}
Hibernate looks at #ManyToOne side when it stores the foreign key.
If you want subcategories of persistent Category to be persisted automatically, you need to configure cascading:
#OneToMany(..., cascade = CascadeType.ALL)