Vincent Partington has more than 10 years of enterprise Java experience. He has written and presented on topics as diverse as performance, security, RIA, and persistence technologies. Vincent Partington is the Chief Technical Officer of XebiaLabs where he is responsible for their flagship deployment automation product Deployit. Vincent is a DZone MVB and is not an employee of DZone and has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

JPA Implementation Patterns: Saving (Detached) Entities

07.24.2009
| 35683 views |
  • submit to reddit

We kicked off our hunt for JPA implementation patterns with the Data Access Object pattern and continued with the discussion of how to manage bidirectional associations. This week we touch upon a subject that may seem trivial at first: how to save an entity.

Saving an entity in JPA is simple, right? We just pass the object we want to persist to EntityManager.persist. It all seems to work quite well until we run into the dreaded "detached entity passed to persist" message. Or a similar message when we use a different JPA provider than the Hibernate EntityManager.

A:link {color: blue;} A:visited {color: #A000A0;} html body p { line-height: 120%; } ul li, ul.menu li, .item-list ul li, li.leaf { list-style-type: disc; list-style-position:outside;} ol li, ol.menu li, .item-list ol li, li.leaf { list-style-type: decimal; list-style-position:outside;}

So what is that detached entity the message talks about? A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence store but that is no longer part of a persistence context (the scope of an EntityManager session). The two most common causes for this are:

  • The EntityManager from which the object was retrieved has been closed.
  • The object was received from outside of our application, e.g. as part of a form submission, a remoting protocol such as Hessian, or through a BlazeDS AMF Channel from a Flex client.

The contract for persist (see section 3.2.1 of the JPA 1.0 spec) explicitly states that an EntityExistsException is thrown by the persist method when the object passed in is a detached entity. Or any other PersistenceException when the persistence context is flushed or the transaction is committed. Note that it is not a problem to persist the same object twice within one transaction. The second invocation will just be ignored, although the persist operation might be cascaded to any associations of the entity that were added since the first invocation. Apart from that latter consideration there is no need to invoke EntityManager.persist on an already persisted entity because any changes will automatically be saved at flush or commit time.

saveOrUpdate vs. merge

Those of you that have worked with plain Hibernate will probably have grown quite accustomed to using the Session.saveOrUpdate method to save entities. The saveOrUpdate method figures out whether the object is new or has already been saved before. In the first case the entity is saved, in the latter case it is updated.

When switching from Hibernate to JPA a lot of people are dismayed to find that method missing. The closest alternative seems to be the EntityManager.merge method, but there is a big difference that has important implications. The Session.saveOrUpdate method, and its cousin Session.update, attach the passed entity to the persistence context while EntityManager.merge method copies the state of the passed object to the persistent entity with the same identifier and then return a reference to that persistent entity. The object passed is not attached to the persistence context.

That means that after invoking EntityManager.merge, we have to use the entity reference returned from that method in place of the original object passed in. This is unlike the the way one can simply invoke EntityManager.persist on an object (even multiple times as mentioned above!) to save it and continue to use the original object. Hibernate's Session.saveOrUpdate does share that nice behaviour with EntityManager.persist (or rather Session.save) even when updating, but it has one big drawback; if an entity with the same ID as the one we are trying to update, i.e. reattach, is already part of the persistence context, a NonUniqueObjectException is thrown. And figuring out what piece of code persisted (or merged or retrieved) that other entity is harder than figuring out why we get a "detached entity passed to persist" message.

Putting it all together

So let's examine the three possible cases and what the different methods do:

Scenario EntityManager.persist EntityManager.merge SessionManager.saveOrUpdate
Object passed was never persisted 1. Object added to persistence context as new entity
2. New entity inserted into database at flush/commit
1. State copied to new entity.
2. New entity added to persistence context
3. New entity inserted into database at flush/commit
4. New entity returned
1. Object added to persistence context as new entity
2. New entity inserted into database at flush/commit
Object was previously persisted, but not loaded in this persistence context 1. EntityExistsException thrown (or a PersistenceException at flush/commit) 2. Existing entity loaded.
2. State copied from object to loaded entity
3. Loaded entity updated in database at flush/commit
4. Loaded entity returned
1. Object added to persistence context
2. Loaded entity updated in database at flush/commit
Object was previously persisted and already loaded in this persistence context 1. EntityExistsException thrown (or a PersistenceException at flush or commit time) 1. State from object copied to loaded entity
2. Loaded entity updated in database at flush/commit
3. Loaded entity returned
1. NonUniqueObjectException thrown

Looking at that table one may begin to understand why the saveOrUpdate method never became a part of the JPA specification and why the JSR members instead choose to go with the merge method. BTW, you can find a different angle on the saveOrUpdate vs. merge problem in Stevi Deter's blog about the subject.

The problem with merge

Before we continue, we need to discuss one disadvantage of the way EntityManager.merge works; it can easily break bidirectional associations. Consider the example with the Order and OrderLine classes from the previous blog in this series. If an updated OrderLine object is received from a web front end (or from a Hessian client, or a Flex application, etc.) the order field might be set to null. If that object is then merged with an already loaded entity, the order field of that entity is set to null. But it won't be removed from the orderLines set of the Order it used to refer to, thereby breaking the invariant that every element in an Order's orderLines set has its order field set to point back at that Order.

In this case, or other cases where the simplistic way EntityManager.merge copies the object state into the loaded entity causes problems, we can fall back to the DIY merge pattern. Instead of invoking EntityManager.merge we invoke EntityManager.find to find the existing entity and copy over the state ourselves. If EntityManager.find returns null we can decide whether to persist the received object or throw an exception. Applied to the Order class this pattern could be implemented like this:

Order existingOrder = dao.findById(receivedOrder.getId());
if(existingOrder == null) {
dao.persist(receivedOrder);
} else {
existingOrder.setCustomerName(receivedOrder.getCustomerName());
existingOrder.setDate(receivedOrder.getDate());
}

The pattern

So where does all this leave us? The rule of thumb I stick to is this:

  • When and only when (and preferably where) we create a new entity, invoke EntityManager.persist to save it. This makes perfect sense when we view our domain access objects as collections. I call this the persist-on-new pattern.
  • When updating an existing entity, we do not invoke any EntityManager method; the JPA provider will automatically update the database at flush or commit time.
  • When we receive an updated version of an existing simple entity (an entity with no references to other entities) from outside of our application and want to save the new state, we invoke EntityManager.merge to copy that state into the persistence context. Because of the way merging works, we can also do this if we are unsure whether the object has been already persisted.
  • When we need more control over the merging process, we use the DIY merge pattern.

I hope this blog gives you some pointers on how to save entities and how to work with detached entities. We'll get back to detached entities when we discuss Data Transfer Objects in a later blog. But next week we'll handle a number of common entity retrieval pattern first. In the meantime your feedback is welcome. What are your JPA patterns?


From http://blog.xebia.com

Published at DZone with permission of Vincent Partington, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Chris Gage replied on Thu, 2009/08/06 - 12:49pm

I found this very useful, and I like your "DIY merge pattern".  But what about the case where, when trying to attach a new object to existing data, I get the message:

Encountered new object "foo" in persistent field "fooCollection<element:foo>" of managed object "bar" during attach. However, this field does not allow to cascade during attach. You cannot attach a reference to a new object without cascading.

Does this mean you can ONLY attach the new object using cascade?  I have had to turn cascade off almost everywhere because JPA, when left to its own devices with cascade, simply cannot thread its way through the referential integrity constraints in this database.  Instead I carefully create a List of the objects in the required order and persist them one by one. 

But now I can't update the saved data because this message seems to insist that I turn cascade on again.

I was hoping that in this case I could persist the new object explicitly and then use your "DIY merge pattern" to update the exiisting record with the pointer to the new data.

 

Vincent Partington replied on Mon, 2009/08/10 - 1:56pm

@Chris Gage: I don't know that particular message but it seems as if you are manually copying a reference to a new object into an existing object and don't have the cascade options configured to cascade the persist operation. If you are using the "DIY merge" pattern you willl need to manually invoke persist on that new object before setting a reference to it on the existing object.

Wallace Ugulino replied on Mon, 2009/09/21 - 5:56pm

Hi, Vincent. Congratulations by your article. Is very useful and very didactics. I want to present you a solution that I've used in my project. First of all, I have to say that this is a research project not concerned with technology itself, but I proposed a method (not java method, hehehe, a method to people work) and I'm developing a software to support the method usage. With the software, I hope to test the method usefulness. You can visit the software, if you want: http://communicatec.uniriotec.br/modus

For several reasons, I can not use JTA. One of the reasons is that my server is a Semprom, with 1GB RAM, and has a lot of services installed, like 2 databases and 2 web containers (TomCat and PHP), and other stuffs.

But, let's go to the code. I've developed a class named Gerenciador.java (in English, something like Manager.java). This class has basic methods to save and remove an entity that has no specific business rules, so we can simply save or remove. This class has a safe way to get the EntityManager instance for the session, and other stuffs.

The method to save entities is posted above:
public void save(T o) {
   getEntityManager().getTransaction().begin();
   try {
       try {
          getEntityManager().persist(o);
       } catch (Exception e) {
          o = getEntityManager().merge(o);
       }
       getEntityManager().getTransaction().commit();
       } catch (Exception e) {
          System.out.print("Error: "+e.getMessage()); // for debug purposes
          getEntityManager().getTransaction().rollback();
   }
}

Do you think that this is an alternative to the pattern you proposed?

Regards,

Wallace Ugulino
(www.ugulino.com.br / www.wallaceugulino.com)

Carla Brian replied on Wed, 2012/08/01 - 5:59pm

Thank you for this one. This is really helpful. I am not yet familiar with this one. I want to sutdy more on this and will research for more tutorials. - Mercy Ministries

Rock Wilson replied on Thu, 2013/07/11 - 7:10am

Thanks for your valuable information guys, its really wonderful to know such kinds of information...

very impressive website...

_________________________

nickel alloy flanges

nickel alloy flanges 

dewa pier 20 in kochi

dewa pier 20 in kochi 

Edward Villanueva replied on Sat, 2013/12/14 - 1:43am

That was an impressive information  . Keep up the good work and keep on sharing useful information. 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.