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);
Related
I'm using Spring Data Jpa and Hibernate on my project.
I have three tables:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
class Parent {
String id;
String name;
}
#Entity
class FirstChild extends Parent {
...
}
#Entity
class SecondChild extends Parent {
...
}
On the first step of my logic I should save Parent object without child type.
And on the second step I know to which Child table it should belong.
For example:
Parent parent = parentRepository.findById("id");
FirstChild firstChild = new FirstChild();
firstChild.setId(parent.getId());
firstChild.setName(parent.getName());
parentRepository.save(firstChild);
But when I do a Hibernate save it throws me exception:
o.h.e.i.DefaultLoadEventListener Load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null
As I understand it doesn't know how to upgrade entity from parent to child type and just throws an exception because of conflict - entity with same id is already there.
Is there any solutions for this problem?
JPA is a means of mapping your Java domain model onto a relational database schema. Since there is no such thing as 'promoting a parent class to a child class' in Java, there is no support in JPA for such an operation.
That being said, you could probably achieve the desired behavior using a native update query. You would need to update the discriminator column (DTYPE) column, and insert a new row into the table corresponding to the child entity (note that in the SINGLE_TABLE strategy, updating the discriminator column would suffice).
A much better solution IMHO, is to delete the parent entity and insert a new child entity. If you're concerned about referential integrity, perhaps you should switch from inheritance to composition.
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.
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.
I have "Parent" and "Child" hibernate entities.
On "Parent" I have a Set<Child> to hold it's children.
When I update the Parent with new children, all works fine: the children are created on "child" table.
But, when I remove one element from the Parent hashset and save, the correspondent child on database is not be deleted.
Here is:
On PARENT (named Workflow):
#OneToMany(orphanRemoval=true, cascade = CascadeType.ALL, mappedBy="workflow", fetch = FetchType.EAGER)
private Set<ActivityDB> activities;
On Child (named Activity)
#ManyToOne
#JoinColumn(name="id_workflow")
#Fetch(FetchMode.JOIN)
private WorkflowDB workflow;
I'm working on persistent instance inside the session. No error is raised. Just seems to work fine, but the register on database still there.
To do a test, I load the Workflow and do a
workflow.activities.remove( activity_index_x )
and then save the workflow using session.update( workflow ).
but the "activity_index_x" still in database and comes to life again when I reload the workflow.
Make sure you go through the manual regarding bidirectional association links.
The best practices include adding the add/remove child methods:
class WorkflowDB {
public void remove (ActivityDB a) {
if (a != null) {
this.activities.remove(a);
a.setWorkflow(null);
}
}
public void add (ActivityDB a) {
if (a != null) {
this.activities.add(a);
a.setWorkflow(this);
}
}
}
But because you use a Set as the one-to-many side, you need to pay extra attention to equals and hashcode. The best way is to use a business-key for checking equality and for the hash-code algorithm, and never use the database identifier for equals/hashcode, especially in conjunction with hash-like data structures (set/map).
Bidirectional associations are more complicated to manage than unidirectional ones. If you don't really need the one-to-many side, you can remove it and replace it with a query instead. That way you'd have to manage only the many-to-one side.
This is caused by the child-to-parent reference not being cleared. Since you mapped both sides (and configured it this way) Hibernate will actually look at the child end of the relation.
The best way to fix this is to also clear the workflow field on the activity when you remove the activity from the workflow (and reversely), so:
class Workflow {
public void remove (Activity a) {
if (this.activities.remove(a)) {
a.setWorkflow(null);
}
}
public void add (Activity a) {
if (this.activities.add(a)) {
a.setWorkflow(this);
}
}
}
The main question is which side of the relation do you want to maintain the relation-state in?
You could also map the relation on the Workflow (do not use the mappedBy attribute, but use a JoinTable annotation to keep the column on the child table) and only map the parent-Workflow as a read-only (insertable=false,updatable=false) field in the Activity.
This way the Workflow is completely in control of which activities are part of it and the activities can still see the workflow they are part of.
class Workflow {
#OneToMany
#JoinTable(...)
private Set<Activity> activities
}
class Activity {
#Column(insertable=false, updatable=false)
private Workflow workflow
}
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.