Detached Objects with JPA Merge Persist Remove


Published: 2020-02-09
Updated: 2020-02-09
Web: https://fritzthecat-blog.blogspot.com/2020/02/detached-objects-with-jpa-merge-persist.html


This is a continuation of my recent article about JPA persist() and merge().
The behavior of these two methods and remove(), concerning detached objects, is subject to this one.

Entity States

Tests

For source code of the entities, relations and methods used here, please see my recent article about persist() and merge().

All tests below are green, i.e. they succeed. Failing JPA calls have been marked by Assert.fail(), and a red left-side border.

Merge Detached

    @Test
public void mergeSucceedsWithDetachedObject() {
final Vehicle vehicle = JpaUtil.transactionalResult(
entityManager, entityManager::merge, newVehicle("Mercedes"));
entityManager.clear();
// vehicle is saved but detached now

final String NEW_NAME = "Daimler-Benz";
vehicle.setName(NEW_NAME);
JpaUtil.transactional( // try to save change in detached entity
entityManager, entityManager::merge, vehicle);

final List<Vehicle> vehicles = JpaUtil.findAll(Vehicle.class, entityManager);
assertEquals(1, vehicles.size());
assertEquals(NEW_NAME, vehicles.get(0).getName());
}

This test constructs a new Vehicle and persist it. Then it clears the persistence context by EntityManager.clear(), we could also use EntityManager.detach(vehicle), leaving the entity detached. It modifies the detached entity and tries to save it using merge(). This succeeds. The merge() method can process detached entities, but mind that its parameter is still detached afterwards, only the return is "managed".

Persist Detached

    @Test
public void persistFailsWithDetachedObject() {
final Vehicle vehicle = newVehicle("Volkswagen");
JpaUtil.transactional(
entityManager, entityManager::persist, vehicle);
entityManager.clear();
// vehicle is saved but detached now

final String NEW_NAME = "VW";
vehicle.setName(NEW_NAME);
try {
JpaUtil.transactional( // try to save change in detached entity
entityManager, entityManager::persist, vehicle);

fail("Persist of detached object must fail!");
}
catch (Exception e) {
// exception is expected here
System.err.println(toDeepest(e).toString());
}
}

The same as above, but this time we use persist() to save the detached entity. The fail() asserts that such an attempt fails, although it would be nice if it succeeded:-) But that's how JPA works. The persist() method can not handle detached entities. Following are the error messages of the JPA providers:

Once again a difference between the JPA providers: EclipseLink doesn't detect the detached state but tries to insert the entity as new.

Remove Detached

    @Test
public void removeFailsWithDetachedObject() {
final Vehicle vehicle = newVehicle("Cadillac");
JpaUtil.transactional(
entityManager, entityManager::persist, vehicle);
entityManager.clear();
// vehicle is saved but detached now

try {
JpaUtil.transactional( // try to remove detached entity
entityManager, entityManager::remove, vehicle);

fail("Remove of detached object must fail!");
}
catch (Exception e) {
// exception is expected here
System.err.println(toDeepest(e).toString());
}
}

This is the same case as persist(), the remove() method can not handle detached objects. Following are the error messages:

Conclusion

We have EntityManager.contains(entity) to find out whether an entity is "managed", but we can't distinguish transient and detached states generically, because sometimes primary keys are evaluated by the application on entity-construction, sometimes by the JPA layer from database sequences on saving the entity.

When you merge() detached objects, be aware that not the object itself changes state then, it is the returned clone only that is "managed" afterwards!

Concerning detached objects, the merge() method shines. You can't update from a detached instance with persist().





ɔ⃝ Fritz Ritzberger, 2020-02-09