Hibernate and parent/child relations - java

I'm using Hibernate in a Java application, and i feel that something could be done better for the management of parent/child relationships.
I've a complex set of entities, that have some kind of relationships between them (one-to-many, many-to-many, one-to-one, both unidirectional and bidirectional).
Every time an entity is saved and it has a parent, to estabilish the relationship the parent has to add the child to its collection (considering a one-to-may relationship).
For example:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
In the same way, if i remove a child then i have to explicitly remove it from the parent collection too.
Child c = (Child) session.load(Child.class, cid);
session.delete(c);
Parent p = (Parent) session.load(Parent.class, pid);
p.getChildren().remove(c);
session.flush();
I was wondering if there are some best practices out there to do this jobs in a different way: when i save a child entity, automatically add it to the parent collection. If i remove a child, automatically update the parent collection by removing the child, etc.
For example,
Child c = new Child();
c.setParent(p);
session.save(c); // Automatically update the parent collection
session.flush();
or
Child c = (Child) session.load(Child.class, cid);
session.delete(c); // Automatically updates its parents (could be more than one)
session.flush();
Anyway, it would not be difficult to implement this behaviour, but i was wondering if exist some standard tools or well known libraries that deals with this issue. And, if not, what are the reasons?
Thanks

One simple way to achieve this type of thing is to add convenience methods to your model classes to ensure that both directions of the bidirectional link are set
public class Parent {
...
public void addChild(Child child) {
this.getChildren.add(child);
child.setParent(this);
}
public void removeChild(Child child) {
this.getChildren.remove(child);
child.setParent(null);
}
This requires some discipline/remembrance on your part to use these methods instead of the direct set/add; but it's best to put commonly used code in a single place.

Related

Proper way to update a Set element in a JPA #OneToMany relationship?

Lets assume we have a bi-directional One-to-Many relationship between Parent and Child.
I like the idea of model that relationship with a Set, because of it intrinsic nature of disallowing duplicates.
Question:
1) What would be the proper JPA way to update a child in such a situation?
Query the Parent and pass an updated Child into it?
Query the Child directly and just call its setters?
2) Has either way some performance advantages or disadvantages?
#Entity
public class Parent extends AbstractPersistable<Long> {
#OneToMany(cascade = CascadeType.ALL, ... )
private Set<Child> children = new HashSet();
public void addChild( Child child ) { ... }
public void removeChild( Child child ) { ... }
// non-anemic domain model ?
public void updateChild( Child child ) {
// how to update the element in the Set?
}
}
UPDATE:
How to properly write the update method? Since Sets in Java do not have a get method?
To update a Child, you don't need to operate the parent collection.
Thanks to the dirty checking mechanism, once the Child becomes managed in the currently running Persistence Context, every change is picked automatically and synchronized to the database.
That's the reason you don't have an update method in JPA. You only have persist or merge in EntityManager.
So, you need to do the following steps:
You load the Child by id:
Child child = entityManager.find(Child.class, childId);
Do the changes on the Child and you are done:
child.setName(newName);

Hibernate CRUD with lists

If I have entitys:
Entity1 has list with Entity2;
Entity2 has list with Entity3;
Entity3 has list with Entity4;
What is operations in my code I have to do when I add new Entity4 in DB?
Just set parent for Entity4 and save Entity4?
or
Set parent for Entity4 and save Entity4. Add Entity4 in list of
Entity3 and save Entity3.
or
Add Entity4 in list of Entity3 and save Entity3. And All Entitys will be update.
It really depends on whether the list maintained by Entity3 is set to cascade operations such as PERSIST, MERGE, and DELETE.
If the list is configured to cascade, then all you'd need to do is:
Set the parent of Entity4.
Add Entity4 to its parent's list and merge the modified parent.
If cascade is not configured, then you'd need to do:
Set the parent of Entity4.
Persist the newly created instance Entity4.
Add Entity4 to its parent's list and merge the modified parent.
Now you may ask why must the parent entity of Entity4 have its list updated and subsequently merged in either case?
That is to make sure that both sides of the association are updated correctly and properly point to one another. Its very likely given the scenario that the parent side of the association is already loaded into the persistence context, so adding the child to the database won't refresh and be visible to an already loaded entity unless its refreshed. The easiest solution in this case is to always modify both sides correctly.
public class ParentEntity {
// add child to the parent entity, maintains association
public void addChild(ChildEntity child) {
if ( child.getParent() != null ) {
child.getParent().removeChild( child );
}
child.setParent( this );
children.add( child );
}
// remove child from parent, disassociates association
public void removeChild(ChildEntity child) {
if ( this.equals( child.getParent() ) ) {
child.setParent( null );
children.remove( child );
}
}
}
I usually find it helpful to expose helper methods like the above on my domain models so that my code doesn't need to be concerned with the associations required to be maintained. I also would likely make the setter for the parent entity's list private so that only Hibernate can use it if needed to force strict use of the addChild / removeChild methods.

OneToMany unidirectional mapping in Hibernate. Issue setting foreign key in referenced entity

Say there are two entities: Parent and Child, with #OneToMany mapping from Parent to Child.
class Parent {
#Column(name="id")
private Long id;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<Child> children;
}
class Child {
#Column(name="id")
private Long id;
#Column(name="parent_id")
private Long parentId;
}
As you can see, in my case, the Child table stores a foreign key to the Parent's primary key. But I don't want that as bi-directional mapping in my Child entity.
The issue arises now is, I'm unable to set the parent_id in Child instances.
I've created instances like this:
Parent parent = new Parent();
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
parentDomainService.save(parent);
Assuming that there is cascading on Parent end. This approach saves the Parent first, then saves the Child instances. And then it runs the update query on child instances to update the parent_id, as I see from the Hibernate show_sql logs. But surprisingly, after update query, I see for some of the child, the parent_id is null. That was surprising to me.
So, I went to handle that thing manually, and removed cascading. Then I saved the entities like this:
Parent parent = new Parent();
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
parent = parentDomainService.save(parent);
for (Child child: parent.getChildren()) {
child.setParentId(parent.getId());
}
childDomainService.save(parent.getChildren());
This one bounced back on me with following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.somepkg.Child
I've seen many questions on SO with that exception, and I know there are many out there, but almost all of them are dealing with bi-directional mapping, or uni-directional mapping with JoinTable. Neither of them suits my situation.
Any lights on this? I'm out of options.
P.S.: The actual scenario I'm dealing with requires saving huge amount of data. E.g.: 50000 parent records, and 250000 Child records. That is why I don't want bi-directional mapping. Because saving Child will do create a query with join table in the back-end.
I'm mostly interested in solution, wherein I don't have to fire query twice on Child table. As that is happening in my current application, and that is hampering the performance.
When you remove cascading the parent does not persist the referenced child elements and at
parent = parentDomainService.save(parent);
the parent references the "unsaved transient" child instances and therefore throws the exception. If you first save the parent and then add the children:
Parent parent = new Parent();
parent = parentDomainService.save(parent);
parent.setChildren(Lists.newArrayList(new Child(), new Child()));
for (Child child: parent.getChildren()) {
child.setParentId(parent.getId());
}
childDomainService.save(parent.getChildren());
then the exception will not be thrown.

Resolving one-to-many relations within transcation returns incomplete collections

Within a complex usertransction (ut) we have to find a JPA entity, add a new child and find the parent entity again. We observe, that the list of children is incomplete when we fetch the parent inside the transaction and complete as expected after the transaction commits. Everything is single threaded and uses the same entity manager instance.
Are we missing something obvious that would explain that behaviour?
ut.begin();
// find the parent entity
ParentEntity parent = em.find(parentKey);
assertEquals(parent.getChildren().size(), 1); // as expected
// add one child
ChildEntity child = createChild(); // create child for the parent
child.setParentRef(parent.getRef());
em.persist(child);
em.flush();
// find the parent entity again
ParentEntity parent = em.find(parentKey);
assertEquals(parent.getChildren().size(), 1); // UNEXPECTED! Should be two...
ut.commit();
// find the parent entity again
ParentEntity parent = em.find(parentKey);
assertEquals(parent.getChildren().size(), 2); // now I see 2 children. After committing
The the relation on parent is defined like:
#OneToMany(orphanRemoval=true, cascade=CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name="parent_ref", referencedColumnName="ref")
List<ChildEntity> children = new ArrayList<ChildEntity>();
This is completely expected. You set the parent of the child, but didn't add the child to the parent list. And all is in a single transaction, so the parent returned by the second call to em.find(parentKey) comes directly from the first-level cache and is thus the exact same object as the one returned by the first call.
It's your responsibility to maintain the coherence of the object graph in a transaction. See http://docs.jboss.org/hibernate/core/4.3/manual/en-US/html_single/#tutorial-associations-usingbidir
You need to add new child to the parent collection:
ChildEntity child = createChild();
child.setParentRef(parent.getRef());
parent.getChildren().add(child);
em.persist(parent);
em.persist(child); // only required if the persist does not cascade to children
em.flush();

Spring Data JPA - loading parent after explicit child deletion returns collection of children with deleted child

I have Parent->Child bidirectional relationship as follows...
class Parent{
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER)
Collection<Child> children;
}
class Child{
#ManyToOne
#JoinColumn(name="PARENT_ID")
private Parent parent;
}
When i delete child explicitly, and after that load its parent (with all children) i get previously deleted child in children collection of parent... JPA provider is Hibernate...
Child child= childRepo.findOne(CHILD_ID);
childRepo.delete(child);
childRepo.flush();
// next, returns collection without deleted child
Collection<Child> children= childRepo.findAll();
Parent parent = parentRepo.findById(PARENT_ID);
/// next, returns collection including deleted child
Collection<Child> parentChildren = parent.getChildren();
I don't understand what is the problem? Every find* method executes select (at list, those SELECTs are logged in console) , but they returns different results...
Your ManyToOne is EAGER (by default). Your OneToMany is also EAGER (you explicitely marked it so). So, when you get a child in your first line of code, JPA also loads its parent, and all the children of the parent.
Then you delete the child, but you don't remove it from the parent's chidren. And since the parent's collection of children is already loaded, the deleted child is still in the collection.

Categories

Resources