JPA EntityManager: Why use persist() over merge()?

Viewed 708367

EntityManager.merge() can insert new objects and update existing ones.

Why would one want to use persist() (which can only create new objects)?

16 Answers

Either way will add an entity to a PersistenceContext, the difference is in what you do with the entity afterwards.

Persist takes an entity instance, adds it to the context and makes that instance managed (i.e. future updates to the entity will be tracked).

Merge returns the managed instance that the state was merged with. It does return something that exists in PersistenceContext or creates a new instance of your entity. In any case, it will copy the state from the supplied entity, and return a managed copy. The instance you pass in will not be managed (any changes you make will not be part of the transaction - unless you call merge again). Though you can use the returned instance (managed one).

Maybe a code example will help.

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
      
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

Scenario 1 and 3 are roughly equivalent, but there are some situations where you'd want to use Scenario 2.

The JPA specification says the following about persist().

If X is a detached object, the EntityExistsException may be thrown when the persist operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time.

So using persist() would be suitable when the object ought not to be a detached object. You might prefer to have the code throw the PersistenceException so it fails fast.

Although the specification is unclear, persist() might set the @GeneratedValue @Id for an object. merge() however must have an object with the @Id already generated.

There are some more differences between merge and persist (I will enumerate again those already posted here):

D1. merge does not make the passed entity managed, but rather returns another instance that is managed. persist on the other side will make the passed entity managed:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2. If you remove an entity and then decide to persist the entity back, you may do that only with persist(), because merge will throw an IllegalArgumentException.

D3. If you decided to take care manually of your IDs (e.g by using UUIDs), then a merge operation will trigger subsequent SELECT queries in order to look for existent entities with that ID, while persist may not need those queries.

D4. There are cases when you simply do not trust the code that calls your code, and in order to make sure that no data is updated, but rather is inserted, you must use persist.

Another observation:

merge() will only care about an auto-generated id(tested on IDENTITY and SEQUENCE) when a record with such an id already exists in your table. In that case merge() will try to update the record. If, however, an id is absent or is not matching any existing records, merge() will completely ignore it and ask a db to allocate a new one. This is sometimes a source of a lot of bugs. Do not use merge() to force an id for a new record.

persist() on the other hand will never let you even pass an id to it. It will fail immediately. In my case, it's:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist

hibernate-jpa javadoc has a hint:

Throws: javax.persistence.EntityExistsException - if the entity already exists. (If the entity already exists, the EntityExistsException may be thrown when the persist operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time.)

You may have come here for advice on when to use persist and when to use merge. I think that it depends the situation: how likely is it that you need to create a new record and how hard is it to retrieve persisted data.

Let's presume you can use a natural key/identifier.

  • Data needs to be persisted, but once in a while a record exists and an update is called for. In this case you could try a persist and if it throws an EntityExistsException, you look it up and combine the data:

    try { entityManager.persist(entity) }

    catch(EntityExistsException exception) { /* retrieve and merge */ }

  • Persisted data needs to be updated, but once in a while there is no record for the data yet. In this case you look it up, and do a persist if the entity is missing:

    entity = entityManager.find(key);

    if (entity == null) { entityManager.persist(entity); }

    else { /* merge */ }

If you don't have natural key/identifier, you'll have a harder time to figure out whether the entity exist or not, or how to look it up.

The merges can be dealt with in two ways, too:

  1. If the changes are usually small, apply them to the managed entity.
  2. If changes are common, copy the ID from the persisted entity, as well as unaltered data. Then call EntityManager::merge() to replace the old content.

Merge won't update a passed entity, unless this entity is managed. Even if entity ID is set to an existing DB record, a new record will be created in a database.

Related