Using Transient Entity in Hibernate to Update/Merge an existing Persistent Object

eipark picture eipark · Mar 15, 2013 · Viewed 10.1k times · Source

I am dealing with a fairly complex object graph in my database. I am using XStream to serialize and deserialize this object graph which works fine. When I import an object graph of an object that exists in the database, it is initially transient, since there are no IDs and hibernate knows nothing of it. I then have business logic which sets IDs on parts of my object graph by figuring out which objects in the newly transient imported object map to existing persistent objects. I then use Hibernate's merge() and saveOrUpdate().

Some pseudocode to give you a better idea of what I'm doing:

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
    }
    ... set a bunch of other IDs deeper in the object graph ...
}

transObj = session.merge(transObj);
session.saveOrUpdate(transObj);

Now this doesn't work as I get errors such as:

   org.springframework.dao.InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]; nested exception is org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]

and it seems like hibernate merge was not meant for associating transient objects to persistent ones.

Is there any way to achieve what I want to do without having to get the persistent object in session, and modifying that, instead of modifying the transient one, and trying to save that and override the existing persistent one?

Answer

Glen Best picture Glen Best · Mar 18, 2013

it seems like hibernate merge was not meant for associating transient objects to persistent

Merge is JPA standard - merges "new & detached entity instances" to "(persistence context-) managed entity instances". Will cascade across FK relationships marked Cascade.MERGE or Cascade.ALL.

From a JPA perspective - no, merge was not meant for associating your Xstream streamed transient objects to persistent. JPA intends for merge to work with the normal JPA lifecycle - create new objects, persistent them, get/find them, detach them, modify them (including adding new objects), merge them, optionally modifiy them some more, then persist/save them. This is deliberate design, so that JPA is stream-lined & performant - each individual object persist to the database doesn't need to be preceded with a retrieval of object state in order to determine whether/what to insert/update. The JPA persistence context object state already contains enough details to determine this.

Your problem is that you have new entity instances that you want to act as if they have been detached - and that's not the JPA way.

SaveOrUpdate is a hibernate proprietary operation - if an entity has an identity it is updated, but if it has not entity then it is inserted.

From a hibernate perspective - yes, merge followed by saveOrUpdate can theoretically work for associating (Xstream streamed) transient objects to persistent, but it may have restrictions when used in conjuction with JPA operations. saveOrUpdate DOES precede each object persist to the database with a retrieve to determine whether/what to insert/update - it's smart, but it's not JPA and it's not the most performant. i.e. you should be able to get this to work with some care and the right configuration - and using hibernate operations rather than JPA operations when conflict occurs.

I get errors such as:

org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]

I believe this might be caused by two factors:

  • somewhere in your (Xstream-created) object graph, a child entity instance is missing that would be present if you did a (cascaded) retrieve using JPA: this gives an object deletion
  • somewhere else in you (Xstream-created) object graph, this same child entity is present: this gives the object being re-saved

    How to debug this: (a) create the object graph from Xstream and print it out IN FULL - every entity, every field; (b) load the same object graph via JPA, cascading the retrieve from the top entity & print it out IN FULL - every entity, every field (c) compare the two - is something missing from (a) that is present in (b)??

Is there any way to achieve what I want to do without having to get the persistent object in session, and modifying that, instead of modifying the transient one, and trying to save that and override the existing persistent one?

Via the debug/fix just suggested - hopefully.

OR my brute-force suggestion to (dumbly) side-step this bug (slows performance a little, though): after you populate IDs in the object graph, call EntityManager.clear(), THEN proceed with merge() and saveOrUpdate(). But UNIT TEST the DB results - to ensure you haven't overlooked something important in populating your Xstream graph.

For testing / as a last resort, try the saveOrUpdate() without the merge(), but you might then be forced to clear() the Entity manager to avoid Hibernate exceptions, such as NonUniqueObjectException.

Let me know if you learn more / have more info B^)