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
- Transient: Any newly constructed entity, not present in database, not contained in any persistence context. Mind that a transient entity can already have a primary key value in case the application sets it (e.g. UUID).
- Persistent ("Managed"):
The entity is present in database, i.e. it has a primary key value,
and it is contained in a persistence context.
This state can be achieved by
EntityManager.persist(entity)
orEntityManager.merge(entity)
, or by reading from database viaEntityManager.find()
orTypedQuery.getResultList()
etc. - Detached:
Such an entity is present in database, i.e. it has a primary key value,
but isn't contained in any persistence context any more.
This state can be achieved by
EntityManager.close()
,EntityManager.clear()
, orEntityManager.detach(entity)
. Web applications that close their entity-manager after a HTTP request ended may hold lots of detached objects in their servlet session context! - Removed:
The entity is still present in database, i.e. it still has a primary key value,
and also is contained in a persistence context, but it is marked for removal.
After the current transaction ended, the entity is not in database any more.
This state can be achieved by
EntityManager.remove(entity)
.
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:
- Hibernate: PersistentObjectException: detached entity passed to persist: ...
- EclipseLink: (fails with an database-specific SQL error message about primary key constraint violation on INSERT)
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:
- Hibernate: IllegalArgumentException: Removing a detached instance ....
- EclipseLink: IllegalArgumentException: Entity must be managed to call remove: ....
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()
.
Keine Kommentare:
Kommentar veröffentlichen