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 to update
) 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.