I have to use Hibernate 4.1.7 in my project. Unfortunately, it's not up to me to move to a newer version.
In my context, I have a master-detail situation. So, I load the master object and show it on a web page, with all its details. I can change both master and details the way I like, even including new details. My saving code is as follows:
@Override @Transactional public void save(Entity entity) { Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(entity); session.flush(); }
Problem comes when I try to delete a detail. The code below shows how it is done:
myMaster.getDetails().remove(myDetail);
I expect Hibernate will track the changes over the master object (it lost a detail instance from the list member), but when I call the save()
method it throws java.lang.IllegalArgumentException: Removing a detached instance xxxx#yyyy
.
I understand the concept if trying to remove a detached instance, but I don't understand why the just removed instance is being considered detached by Hibernate.
Any ideas?
TIA!
1 Answers
Answers 1
All you need to do to make it work the way you expect (to let Hibernate check the entire object graph) is this:
@Override @Transactional public Entity save(Entity entity) { return entityManager.merge(entity); }
Of course, make sure that MERGE
operation is cascaded from master to details (CascadeType.MERGE
or CascadeType.ALL
) and that orphanRemoval
is set for details collection. If orphanRemoval
is not suitable, then you have to remove the details explicitly (otherwise Hibernate would delete associated children when they stopped being associated with parents, which is obviously not desirable).
Also, make sure to use the result of the merge
operation afterwards as it always returns a copy of the passed-in detached instance (passed-in instance is not modified). This is especially important when persisting new instances because ids are set in the copy.
However, you pay the penalty of reloading the object graph from db, but, if you want everything done automatically, Hibernate has no other way to check what has actually changed, other than comparing it with the current state in the db.
Now the explanations of what you observed in your attempts:
The master instance you save is detached. You load it in one transaction (persistence context/session), and save it in another.
It works fine with
saveOrUpdate
when you update it:Either save(Object) or update(Object) the given instance, depending upon resolution of the unsaved-value checks (see the manual for discussion of unsaved-value checking).
Parameters:
object - a transient or detached instance containing new or updated state
As stated in the doc, it is intended to work with detached and transient instances.
You tried merging, but you got an exception telling you that there is already an object with the same id. The only explanation is that you tried something like this:
@Override @Transactional public void save(Entity entity) { entity = entityManager.merge(entity); Session session = entityManager.unwrap(Session.class); session.saveOrUpdate(entity); session.flush(); }
Then an exception is thrown as described in the
update
javadoc:Update the persistent instance with the identifier of the given detached instance. If there is a persistent instance with the same identifier, an exception is thrown.
So, you merged the instance into the current persistence context, and then tried to
saveOrUpdate
it (which delegated toupdate
) and that resulted in the exception, as you must not update the detached instance while there is already a persistent one in the current persistence context (otherwise the persistent one would become stale).You don't have to do this, just merge, and at the end of the transaction Hibernate will dirty-check the objects and flush the changes to the db automatically.
0 comments:
Post a Comment