Skip to end of metadata
Go to start of metadata
Gliffy Zoom Zoom conversation lifecycle

Goals

1. Load JPA entities during a long-lived conversation with multiple read/update cycles across multiple servlet requests, deferring committing changes until the entire conversation has concluded
2. Preserve the ability to configure lazy-loaded associations on JPA entities

Problems:

The main problem is that uninitialized lazy-loaded associations on detached entities can not be portably read except under an open EntityManager context. In the case of transactional entity managers, the EntityManager is closed when each transaction commits (on each request) leaving loaded entities detached. In the case of extended entity managers special handling is required to ensure the extended EntityManager is not associated with transactions under the conversation, and then to ensure that it is explicitly joined on the last transaction when the conversation is concluded.

A: Access to lazy-loading relationships in view

By default the JPA EntityManager and current transaction are closed before view rendering. This presents a problem for views which need to access uninitialized lazy-loaded associations - if those associations are accessed outside the context of an open EntityManager behavior is undefined. Hibernate yields an exception while EclipseLink has special support for read-only operations outside of a closed EntityManager.

The typical (portable) remedy is to use the OpenEntityManagerInViewFilter or OpenEntityManagerInViewInterceptor which creates and holds open the EntityManager for the lifetime of the handler/request, including view rendering.

Provider

Result

EclipseLink

Works out of the box without additional filters/interceptors. Apparently the EclipseLink behavior is to transparently support reads of relationships on detached entities

http://dev.eclipse.org/mhonarc/lists/eclipselink-users/msg05748.html

Hibernate

throws exception as per spec

B: Updates/Reads

Detached entities must be able to be read and updated without participating in the current transaction.

C: Merging

Merges don't cascade automatically unless specified in the annotation on the relationship

merge takes a plain Java object or detached entity, attaches it to the entity manager context for save-or-updates, and returns a reference to the newly-attached entity instance.

Associations must be annotated with cascade-all or cascade-merge in order for merges to cascade from merged object through nested associations. This implies lazy-loading of all associations which are subject to modification be addressed - either by pre-emptive/eager-loading or custom/DIY merging.

merge interaction with lazy-fetched associations

There is a difference of behavior between EclipseLink and Hibernate. It appears there is some dispute on interpretation of merging of lazy-fetched associations.
See: https://hibernate.onjira.com/browse/HHH-4135

We had a team discussion on this topic today and the conclusion is that we do not violate the JPA spec but we do "violate it in spirit" but for good reasons (perf optimization and memory consumption limitation).
I have detailed the reasons on HHH-5187 as well as proposed a possible enhancement.

More discussion here: https://hibernate.onjira.com/browse/HHH-5187

Provider

 

EclipseLink

Unloaded lazy-fetched associations will be merged when the parent entity is merged. Re-attachment of parent entity via merge is not required prior to modification of unloaded lazy association (e.g. modify before save of parent entity). I.e. the EclipseLink lazy association either has some intelligence that allows modification in a detached state, or EclipseLink can determine the EM context outside of the spec as mentioned above in discussion of EM-in-views. This appears to be an explicit feature of EclipseLink, see:

https://forums.oracle.com/forums/thread.jspa?messageID=1706796

Hibernate

The detached entity must first be attached before manipulation of lazy association. Subsequent manipulation and second merge succeeds. If the detached parent entity is not explicitly merged before manipulation of the lazy association, the following exception occurs (which makes sense because the nested association is not at the time of modification attached to any context).

 Expand source
Merging of parent object

Hibernate JPA requires merging of the parent entity in order to read unloaded lazy-fetched associations. Merging will cause an update of the merged parent entity on commit of the transaction (whether EntityManager flush mode is AUTO or COMMIT). This is clearly undesirable, so an alternate solution must be devised for the combination of detached entity and uninitialized lazy-fetched associations.

Since EclipseLink can load uninitialized lazy-fetched associations on detached entities (as per above), merging is not necessary and this problem does not occur.

Solutions

Eager-loading all associations prior to session storage

The most straightforward solution is to ensure that all lazy associations on entities which will be detached and stored into the session have been pre-emptively loaded prior to storage in the session. This can be done via an interceptor (or advice) which is run after the controller, but before the EntityManager/transaction is closed, and which identifies JPA entities and recursively loads all lazy associations. To support the general case of cascading merges from a top-level JPA entity through its object graph, the loading must be done recursively and comprehensively.

Custom Merging/Reintegration

JPA does not support the semantic of re-attaching an entity without requesting an update. If lazy associations are not pre-emptively loaded, we must devise a way to re-attach a detached entity such that its lazy assocations can be loaded in a new EntityManager, yet in a way that avoids database commits of the re-attached entities. In practice it appears sufficient to manually create a new temporary EntityManager which is not associated with a transaction to re-attaching the detached entity and perform reads/updates. Such an EntityManager does not appear to cause any db commits (in fact will throw an exception if flush is called since it is not associated with a transaction).

On conclusion of the conversation, all deferred updates can be committed by simply merging the updated session object into a transactional EntityManager.

The idiom above can be made somewhat less cumbersome by AOP and injection of an EntityManager interface specialization which supports "reattaching", however it still requires some cooperation from application code (cannot be done strictly declaratively).

Extended EntityManagers

A third strategy for avoiding issues with loading of lazy associations on detached objects is to arrange for the object to never be detached - this can be done with an Extended EntityManager. An Extended EntityManager is a nominally container-managed EntityManager (in that the container still injects it) whose lifecycle is governed by the application. Under Spring the definition of "container" is blurred - the lifecycle of a Spring-injected Extended EntityManager is that of the bean it is injected into. While on the surface this is not very useful (i.e. singleton, prototype, request scopes) it's possible to define custom scopes in Spring to bring the Extended EntityManager back under application control.

If created under a running transaction, Extended EntityManagers will automatically join the transaction. Since the goal is to defer comitting changes, this is undesirable, and the application is generally responsible for arranging the Extended EntityManager to be created outside of a transaction, and only explicitly joining it to a transaction (via joinTransaction) upon conclusion of the conversation. All updates on an Extended EntityManager are deferred until a transaction is joined (and then committed).

While using an Extended EntityManager helps avoid the problems with lazy-loading in detached entities it brings its own problems:

  • The application must be re-implemented to support constructing and interacting with the extended EntityManager outside the context of a transaction, and explicit support for joining the transaction at the conclusion of the conversation. Given pervasive use of Spring declarative transactions, managing both transactional and non-transactional paths explicitly could complicate code significantly.
  • In the case of a rollback, entities will be detached from the Extended EntityManager and one must implement compensating code to reattach entities - this brings us back to the initial problem with transactional EntityManagers.
Extended EntityManager notes

I found some dispute over when/how extended container-managed EntityManagers are actually closed in Spring: http://forum.springsource.org/showthread.php?27430-Injection-of-PersistenceContext-with-PersistenceContextType-EXTENDED

It appears since Spring is a layer between "the container" and "your application" it's not clear who actually closes the extended container-managed EM. In EJB this would be handled by the @Remove annotation.

http://stackoverflow.com/questions/4649334/extended-persistence-context-type-with-jpa-2-hibernate

Rollback in JPA is unrecoverable - all objects are detached from the persistence context.

http://weblogs.java.net/blog/blog/davidvc/archive2007/04/jpa_and_rollbac.html

I didn't get a lot of satisfactory answers, but then I got this great answer from Ecmel Ercan on the Glassfish persistence alias, and I think he really nails the right design pattern. Basically, you never keep JPA objects attached or "managed" between transactions.

When the flush mode is AUTO, the Entity-Manager performs a flush operation automatically as needed. In general, this occurs at the end of a transaction for transaction-scoped EntityManagers or when the persistence context is closed for application-managed or extended scope EntityManagers.

http://developeriq.in/articles/2012/jan/25/extended-persistence-context-and-application-scope/

Hibernate provides a native MANUAL flush mode, but this is not in JPA spec.

How/when do changes to an extended entity manager commit

According to JBOSS: http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/transactions.html#transactions-demarcation

This is perfect to implement the entitymanager-per-conversation pattern. A stateful session bean represents the conversation implementation. All intermediate conversation work will be processed in methods not involving transaction. The end of the conversation will be processed inside a JTA transaction. Hence all queued operations will be executed to the database and committed.
...

5.3.2. Application Managed Entity Manager

Application-managed entity manager are always EXTENDED. When you create an entity manager inside a transaction, the entity manager automatically join the current transaction. If the entity manager is created outside a transaction, the entity manager will queue the modification operations. When

entityManager.joinTransaction() is called when a JTA transaction is active for a JTA entity manager
entityManager.getTransaction().begin() is called for a RESOURCE_LOCAL entity manager
the entity manager join the transaction and all the queued operations will then be executed to synchronize the persistence context.

It is not legal to call entityManager.joinTransaction() if no JTA transaction is involved.

This appears to imply the extended entity manager will eagerly join (when constructed within the scope of spring and spring declarative transactions) any existing transaction and automatically flush modifications.
To avoid this flush it is necessary to construct and interact with the extended entity manager outside of any transaction.
Given that we rely extensively on ubiquitous spring declarative transactions, it could be quite complicated to support parallel non-transactional operations in our web and service layers.

Confirmed: (Extended) EntityManagers created outside of the context of a spring-declared transaction have no transaction associated with them and will not commit any changes whatsoever on close or flush (not sure if this is per-spec behavior or due to wrapping and quelling of these invocations by Spring). joinTransaction must be called at some point (usually upon completion of work) in order to associate the entity manager with the transaction - this association will cause the implicit flushing of the EM on subsequent transaction commit.

Detection of load-stated of lazy-load associations

http://docs.oracle.com/javaee/6/api/javax/persistence/PersistenceUtil.html#isLoaded(java.lang.Object, java.lang.String)

  • No labels