Let's have a look at some em.merge related fun.
From discussions on a number of forums, here is my explanation for what goes on when you call em.merge.
We will use the following classes as an example:
@Entity
public class Parent {
...
private List<Child> children;
...
public List<Child> getChildren() { return this.children; }
public void setChildren(List<Child> children) { this.children = children; }
...
}
@Entity
public class Child {
...
private Parent parent;
...
public Parent getParent() { return this.parent; }
public void setParent(Parent parent) { this.parent = parent; }
...
}
To aid in understanding, we will assume that our client has a UserTransaction ut
(this is to allow us to force entity instances to become detached. Entity instances can become
detached without using a UserTransaction, however, we want to show what happens to the entities;
So our client goes something like:
Parent parent = null;
...
ut.begin();
parent = em.find(Parent.class, aParentId);
...
At this point the client has a reference to a managed instance. We can illustrate this like so:
Now at some point we have to commit the transaction and the transaction ends.
...
ut.commit();
...
So now parent still points to an instance of the class Parent that still holds the data as
before, however, the instances have lost their connection to the entity manager (as the
connection depended on the transaction.
We can illustrate this like so:
Later on, we want to synchronise the parent object back to the database, so we call em.merge:
...
ut.begin();
Parent managedParent = em.merge(parent);
...
Now, the transaction is still active, so the instances pointed to by managedParent are still
managed instances, i.e. changes made to fields will be reflected back into the database.
The instances pointed to by parent are still detached instances, i.e. changes made to fields
do not get persisted back to the database. em.merge has done two things for us:
- Fetched new managed instances of the objects we already have
- Synchronised all the changes in the instances we have back to the managed instances (and therefore back to the database
We can illustrate this like so:
Once we commit or otherwise end the transaction, we will now have two sets of detached instances.
Some people want to not have references to our old detached instance lying around... so they will
typically reuse the parent instance variable like so:
...
ut.begin()
parent = em.merge(parent);
...
This is OK, provided that there are no other references to the detached entities lying around.
If you had done something like
...
Child child = parent.getChildren().get(0);
ut.begin();
parent = em.merge(parent);
ut.commit();
...
Then you are in a dangerous situation as child points to a different detatched instance of the same persisted entity!!!
The moral of the story is that when you call em.merge, you need to know what instances you are merging and what you will do with the detatched instances after you have finished merging
Thank you for your very clear explanation with diagrams (a picture is worth a thousand words) !!
ReplyDeleteThat's a great post, thanks! Another confusing area in my opinion is what happens when a few children are deleted while detached (i.e., parent.getChildren() returns < 3 items). In that case, invoking:
ReplyDeleteParent managedParent = em.merge(parent);
System.out.println(managedParent.getChildren().size());
will indeed print the correct number of children, and further inspection of the children suggests that the removed child no longer exists. However, the child HAS NOT BEED REMOVED FROM THE DB.
In my tests that holds true even if I manually flush(). Doing...
parent = em.find(Parent.class, aParentId);
does not return the removed child (due to caching), but if I restart the server and invoke find again, the removed child is returned.
Would be great if you could explain what I am missing, thanks!
Clear explanation, however, the pictures are not visible.
ReplyDelete@Csaba Yep unfortunately the webserver on which I was hosting those images has been lost...
ReplyDelete