Is there a way to fix the JPA EntityManager?
2012/04/25 6 Comments
Using JPA is easy for small projects but has well hidden problems which are caused by some very basic design decisions. Quite a few of them are caused because the EntityManager cannot be made Serializable. Although there are some JPA providers which claim serializability (Hibernate) they aren’t!
Is the EntityManager Serializable?
The LazyInitializationException is a pretty bad beast if you ever worked with EJB managed EntityManagers. That problem caused lots of people to discover alternative ways. Two of the most prominent are JBoss Seam2 if you are working with the JBoss stack and Apache MyFaces Orchestra for Spring applications.
The basic problems are summed up very well in the at large still correct Apache MyFaces Orchestra documentation:
Apache MyFaces Orchestra Persistence explanation
If you read through the whole page, you will see the TODOs at the very bottom of the page:
TODO: is the persistence-context serializable? Are all persistent objects in the context always serializable?
The simple answer is: NO not at all! Neither the EntityManager nor the state in the entities are Serializable as per the current JPA specification!
Why is the EntityManager not Serializable
There are a few reasons:
1. Pessimistic Locking
The biggest blocker first: JPA doesn’t only support Optimistic Locking but also Pessimistic Locking. You can either declare this in your persistence.xml and also programmatically via the
LockModeType in many functions.
EntityManager#find(java.lang.Class entityClass, java.lang.Object primaryKey, LockModeType lockMode) EntityManager#lock(java.lang.Object entity, LockModeType lockMode) ...
But if you ever use pessimistic locking (a real hard lock on the database) the connection is bound to the database and cannot be ‘transferred’ to another EntityManager without losing the lock.
2. Id and Version fields are optional
To use the optimistic locking approach, a primary key plus some ‘version’ field must be used in the entity:
UPDATE tableX SET([somevalues], version=:oldversion+1) WHERE id=:myId AND version==:oldversion
Obviously this update can only succeed once. Trying to update the row a second time will not find any database entry because the
version==:oldversion will not be true anymore.
When you use optimistic locking in JPA, you will always have such a ‘version’ column already. But there is no need to specify it yet! Thus this information will not be transported if you serialize the entity!
To fully support optimistic locking, those entities will need mandatory
3. Losing the entity state information
As outlined in a previous blog post every JPA entity will get ‘enhanced’ with some magic code which tracks
_dirty state information. Those BitFlags will track the parts of the entity which got changed or fetched lazily.
The problem in this area is mostly caused by the JPA spec which by default prevents the JPA providers from serializing the ‘enhanced entities’ but requires serializing the ‘native’ information. At least that seems to be the common understanding of the following paragraph in the JPA spec:
„Serializing entities and merging those entities back into a persistence context may not be interoperable across vendors when lazy properties or fields and/or relationships are used.
A vendor is required to support the serialization and subsequent deserialization and merging of detached entity instances (which may contain lazy properties or fields and/or relationships that have not been fetched) back into a separate JVM instance of that vendor’s runtime, where both runtime instances have access to the entity classes and any required vendor persistence implementation classes.
Of course, most JPA providers know a way to enable the serialization of the state fields. In OpenJPA just provide the following magic properties to your persistence.xml:
<property name="openjpa.DetachState" value="loaded(DetachedStateField=true)"/> <property name="openjpa.Compatibility" value="IgnoreDetachedStateFieldForProxySerialization=true"/>
This will also serialize _loaded and _state BitFlags along with your Entity.
The problem with having the EntityManager not Serializable
Well, this one is easy:
- You cannot store the EnityManager in a Conversation
- You cannot store the EntityManager in a Session
- You cannot store the EntityManager in a JSF View
- No clustering, because Clustering means that you need to Serialize the state
What can you do today?
Today the only working sulution is the entitymanager-per-request pattern. Basically creating a
@RequestScoped EntityManager e.g. via a CDI
@Produces for each and every request. That also means that you need to manually merge those entities on the callback. If you use JSF that is in your action.
How to fix the JPA EntityManager in the future?
Here are my thoughts about how we can do better in the future. Please note that there is a project called Avaje eBean which is not JPA compliant but has already successfully implemented those ideas.
Provide an OptimisticEntityManager
public interface OptimisticEntityManager extends EntityManager, Serializable
The most important change here is that it implements the
OptimisticEntityManager should throw an
NonOptimisticModeException whenever one tries to execute an operation on the EntityManager which requires a non-optimistic
LockModeType or another operation which creates some lock or non-serializable behaviour.
There should be a way to explicitly request an OptimisticEntityManager, e.g. via
Make @Id and @Version mandatory for those Entities
This will solve the problem with losing the optimistic lock information when serializing.
Define _loaded and _dirty Serialization
The future JPA spec could either clarify that serialization is more important than JPA-vendor inter-compatibility (who uses 2 different JPA providers in the same environment anyway?).
Or just specify that 2 BitFlags can be passed in the Serialized entity and how they should behave.
Please tell me what you think? Do we miss something? It’s not an easy move, but up to now I think it is doable!
PS: Thanks to Shane Bryzak and Jason Porter for helping me get rid of the worst English grammar and wording issues at least. Hope you folks got the gist regardless of my bad english ;)