I've been looking around the net for a decent answer to this but all I've gotten is confused. I'm struggling with how #ManyToOne annotations in hibernate are supposed to work - because #OneToMany with #JoinColumn seems far superior because hibernate inserts the foreign keys properly instead of me having to assign the child object in java before saving.
Basically as the most simple example I have something like this:
Parent Table: id int,
Child Table : id int, parentFk int
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany
#JoinColumn(name = "parentFk")
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#Column
private Integer parentFk;
//setters and getters
}
Now if I send some json that maps to a Parent class and ask hibernate to save it, it will save everything in the table including the new id of the parent in the parentFk field. However I've been lead to believe that this is actually the wrong way of doing things and that I should be doing #OneToMany(mappedBy = "id") in the Parent class instead - and then also having #ManyToOne with #JoinColumn and a Parent object in the Child class.
The problem is doing it this way I have to manually set the parent object in the child via java code before hibernate will save the id of the parent in the parentFk field correctly...it just seems like a very long winded of doing something that is already working perfectly for me (albeit I cannot access the parent object from the child).
Moreover I've tried to remove the parentFk field in the child object and use #ManyToOne with a parent object reference, but hibernate doesn't seem to like it. Am I doing this all wrong?
Your mapping should be something like this.
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="parent", cascade={CascadeType.ALL})
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name="parentFk")
private Parent parent;
//setters and getters
}
You should also have an add(Child child) method on your parent (and remove) to manage the relation ship. And assuming Parent and Child are in the same package I would make the setParent on the Child package protected, i.e no access modifier (setParent(Parent parent) { this.parent=parent}).
public void add(Child child) {
child.setParent(this);
this.children.add(child);
}
When doing it like this hibernate will be able to execute the right queries.
Fixed by using #JsonManagedReference on the #OneToMany mappings and #JsonBackReference on the #ManyToOne
Related
I'm trying to make an #OneToOne mapping following the https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ the mapping itself works but its triggering an N+1 query problem.
The query is being made on the parent entity service and its triggering N+1 queries.
How can I improve this code to only make 1 query? We don't need to access the ParentDetails in this case.
EDIT: I've tried using JPQL and LEFT JOIN FETCH ParentDetails and didn't work either.
EDIT2: Just to try to add more information. I've put a breakpoint on the getParentDetails just to make sure I was not calling the getter anywhere and I'm not calling and double-checked and it seems a join problem on the repo call.
Let's go to the code:
Parent
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "parent")
private ParentDetails parentDetails;
// Getters, setters, etc omitted for brevity
}
ParentDetails
#Entity
public class ParentDetails {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
// Getters, setters, etc omitted for brevity
ParentDetailsRepository
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {
Page<Parent>findByNameOrderByName(#Param("name") final String name,final Pageable pageable);
}
Hibernate executes the additional queries because the Parent entity doesn't map the foreign key column. Hibernate doesn't support lazy fetching for that end of the association. When Hibernate instantiates a Parent object, it needs to check if it needs to initialize the association with a proxy or a null object. And at some point, the team decided that they would fetch the associated entity if they are forced to perform a query anyways. I explained that in more detail here: https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one
If you want to avoid the additional queries, you need to model a unidirectional association between ParentDetails and Parent. In your example, that would mean that you need to remove the parentDetails attribute from your Parent entity.
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getters, setters, etc omitted for brevity
}
Because your ParentDetails entity uses the same id value as the Parent entity, you don't need a bidirectional association. If you want to get the ParentDetails entity for a Parent entity, you can get it with a call of the em.find(...) method
Parent p = // fetch the parent object ...
ParentDetails pd = em.find(p.getId(), ParentDetails.class);
Let's say I have 2 entities like that
class Parent {
String name;
Child children;
}
class Child {
String name;
Parent parent;
}
The thing is that I don't want to allow deleting child from database if it's associated with any Parent -> child in parent can't be null. Is there any way to do it? I could just check it with some forloop everytime i try to delete child from db (like query all parents and check their childId), but it doesn't seem to be very efficient.
Probably your sample should be:
public class Parent {
#Id
#GeneratedValue
private long id;
#OneToMany(optional=false)
private Set<Child> children;
// getter/setter
...
}
public class Child {
#Id
#GeneratedValue
private long id;
private String name;
// getter/setter
...
}
So take a look in this annotation: #OneToMany(optional=false), it is for enforcing NOT NULL constraint.
Here you can find more information: http://docs.jboss.org/hibernate/core/4.2/manual/en-US/html_single/#d5e5674
Your question is not well defined, but i will asume you are using tags like #Entity since you put the Hibernate and jpa tags.
In this case you should use a #OneToMany relationship (Parent -> child) and #ManyToOne in (Child -> Parent). If you do so one of them will store the id(or whatever you use as id column). When you do the addParent method and removeParent (from Child) just don't delete the other and that's it.
Recently i got this error,
can not handle managed/back reference 'defaultreference' in jackson for composite key
I googled alot but found the below option to use,
JsonManagedReference and JsonBackReference
Reference
But my situation is,
Class Parent{
private int id;
#JsonManagedReference
Set<Child> childSet;
}
Class Child{
private ChildId childId;
private String name;
}
Class ChildId{
private int childKey;
#JsonBackReference
private Parent parent;
}
As you see, in the child class it has a composite key. I can not change this since it has relationship with DB.
Can anybody help me with this issue?
Note:
I'm using Jackson 2.4.3
I'm using Javers 1.2.9 for Object comparison
Update1:
As per suggestion, I have removed JsonManaged and JsonBack reference annotations and added JsonIgnore to Parent attribute in childId Class.
But im getting below error with Javers,
JaVers runtime error - diff for Set of ValueObjects is not supported
The issue is resolved.
The most weird way of solving.. ;)
Removed #JsonManagedReference in Parent.
Add #JsonBackReference in Parent object which is in Child's Id object.
Ex:
Class Parent{
private int id;
Set<Child> childSet;
}
Class Child{
private ChildId childId;
private String name;
}
Class ChildId{
private int childKey;
#JsonBackReference
private Parent parent;
}
Do you see this exception on deserializing JSON to Java objects?
If yes, the workaround that I used was to -
1. Remove the #JsonManagedReference and #JsonBackReference from the entities.
2. #JsonIgnore the Parent reference (for ex, in your ChildId class). So parent reference in ChildId is null on serializing.
3. To deserialize, send two seperate entities (Child and Parent) back to the service. Once both the objects are available, I set the Parent back into the ChildId class which helps to satisfy the circular reference.
I am trying to configure this #OneToMany and #ManyToOne relationship but it's simply not working, not sure why. I have done this before on other projects but somehow it's not working with my current configuration, here's the code:
public class Parent {
#OneToMany(mappedBy = "ex", fetch= FetchType.LAZY, cascade=CascadeType.ALL)
private List<Child> myChilds;
public List<Child> getMyChilds() {
return myChilds;
}
}
public class Child {
#Id
#ManyToOne(fetch=FetchType.LAZY)
private Parent ex;
#Id
private String a;
#Id
private String b;
public Parent getParent(){
return ex;
}
}
At first, I thought it could be the triple #Id annotation that was causing the malfunction, but after removing the annotations it still doesn't work. So, if anyone have any idea, I am using EclipseLink 2.0.
I just try to execute the code with some records and it returns s==0 always:
Parent p = new Parent();
Integer s = p.getMyChilds().size();
Why?
The problem most probably is in your saving because you must not be setting the parent object reference in the child you want to save, and not with your retrieval or entity mappings per se.
That could be confirmed from the database row which must be having null in the foreign key column of your child's table. e.g. to save it properly
Parent p = new Parent();
Child child = new Child();
p.setChild(child);
child.setParent(p);
save(p);
PS. It is good practice to use #JoinColumn(name = "fk_parent_id", nullable = false) with #ManyToOne annotation. This would have stopped the error while setting the value which resulted in their miss while you are trying to retrieve.
All entities need to have an #Id field and a empty constructor.
If you use custom sql scripts for initialize your database you need to add the annotation #JoinColumn on each fields who match a foreign key :
example :
class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
public Parent() {}
/* Getters & Setters */
}
class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
/* name="<tablename>_<column>" */
#JoinColumn(name="Parent_id", referencedColumnName="id")
private int foreignParentKey;
public Child () {}
}
fetch= FetchType.LAZY
Your collection is not loaded and the transaction has ended.
I am looking to build a tree based hierarchy.
#Entity
class Category {
#Id
private String id;
#Column
private String name;
#ManyToOne
private Category parent;
}
Should the above declaration be ManyToOne or OneToOne? I am planning to use ManyToOne. Is this correct?
ManyToOne, if you plan to have a tree, as a parent can have more than one children. Look at this example from Hibernate's test suite: https://github.com/hibernate/hibernate-core/blob/master/hibernate-core/src/test/java/org/hibernate/test/annotations/manytoone/Node.java