Introduction
I decided to share a special situation that I lived when I attempted to improve my code following some JEE specification principles. But as always, the thing is not easy. I realised that the two possible solutions did not work because of hibernate bug. Therefore I had to adapt my solution with a hibernate workaround.
Initial situation
We have the screen that shows:
§ Entity A instance name property.
§ Description properties from B instances linked with A instance.
§ Description properties from C instances linked with A instance.
§ Description properties from D instances linked with A instance.
§ Description properties from E instances linked with A instance.
In the server side we could have to following class diagram (we do not care about best practises):
The business object model for this example is the following:
We have to mention important points here:
§ The find method code in the Server Component is:
String queryStr = "select distinct a from A a where a.id = :id";
Query query = em.createQuery(queryStr).setParameter("id", id);
A a = query.getSingleResult();
§ The extended persistence context is binding to the life cycle of the Client Component.
§ All the relationships from entity A are using Lazy Loading Strategy.
§ When the user interface is rendered in the server side, from A instance, we can get all its relationship instance properties because A is attached to the persistence context.
If this approach is working, Why Do I want to swap from extended persistence context to transactional?
The main reason is the following:
Aurelio - The Guru says: Any change in the database that has been done outside our scope (other user) and affects an entity that appears in the extended persistent context will potentially create inconsistencies once the extended persistence context is NOT notified from DB that an entity that it "caches" has been changed, so by that time the data in that entity is stale (dirty). To workaround this, you have to explicitly refresh() that entity from PC every time you query that, so, what the point of extended persistence context?.
Note: The solution is not just change from extended to transactional persistence context. In this case happens the following:
When the user interface is rendered, the entity A instance is detached (the transactional persistence context does not exist anymore after finishing the transaction), therefore we can not access to the lazy loading collections linked to entity A instance.
Each vendor solves this situation in a different way (i.e Hibernate: org.hibernate.LazyInitializationException: failed to lazily initialize a collection - no session or session was closed).
Solution - First Approach with transactional persistence context.
The server side we could have to following class diagram:
The business object model for this example is the following:
We have to mention important points here:
§ The code In the Application Service find method is:
String queryStr = "select distinct a from A a LEFT OUTER JOIN FETCH a.b “ +
“LEFT OUTER JOIN FETCH a.c ” +
“LEFT OUTER JOIN FETCH a.d ” +
“LEFT OUTER JOIN FETCH a.e ” +
“where a.id = :id";
Query query = em.createQuery(queryStr).setParameter("id", id);
A a = query.getSingleResult();
§ The transactional persistence context is binding to the transaction.
§ All the relationships from entity A are using Lazy Loading Strategy.
§ When the user interface is rendered in the server side, from A instance, we can get all its relationship instance properties because all of them were fetched in the query.
Solution - Second Approach with transactional persistence context.
The server side we could have to following class diagram:
The business object model for this example is the following:
We have to mention important points here:
§ The code In the Application Service find method is:
String queryStr = "select distinct a from A a where a.id = :id";
Query query = em.createQuery(queryStr).setParameter("id", id);
A a = query.getSingleResult();
§ The transactional persistence context is binding to the transaction.
§ All the relationships from entity A are using Eager Loading Strategy.
§ When the user interface is rendered in the server side, from A instance, we can get all its relationship instance properties because all of them were fetched in the query because are declared Eager.
What a Surprise! These solutions do not work with Hibernate. Hibernate´s bug.
For both approaches we get the next exception:
org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
After investigating I found this website:
The problem is described as:
“For class X, multiple collection fields are marked for eager fetching: a, b, c; this is not supported by Hibernate in releases after 3.2.x“
Hibernate workaround with transactional persistence context.
The business object model for this example is the following:
We have to mention important points here:
§ The code In the Application Service find method is:
String queryStr = "select distinct a from A a where a.id = :id";
Query query = em.createQuery(queryStr).setParameter("id", id);
A a = query.getSingleResult();
§ The persistence context is binding to the transaction.
§ All the relationships from entity A are using Eager Loading Strategy.
§ Hibernate workaround:
Add a new annotation @Fetch(value = FetchMode.SUBSELECT) in the collections.
@JoinTable(… …
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
@JoinTable(… …
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
@OneToMany(fetch = FetchType.EAGER, … …)
@Fetch(value = FetchMode.SUBSELECT)
@OneToMany(fetch = FetchType.EAGER, … …)
@Fetch(value = FetchMode.SUBSELECT)
§ When the user interface is rendered in the server side, from A instance, we can get all its relationship instance properties because all of them were fetched in the query because are declared Eager.
Hmmm... I didn't have the time to read the whole post carefully, but it seems to me that you could have solved the initial problem (a different user changing the underlying data) with simple versioning. Have you maybe looked into that approach?
ReplyDeleteHi Veggen,
ReplyDeleteI have heard about this approach but I do not have a deep knowledge about that.
Please, Could you give here an explanation about that?.
Well, the idea behind versioning is that you have a special (version) column in which the value is incremented every time the row is updated. Just prior to the update, that value of the current entity is compared with the one in the database, if they are different an (optimistic locking) exception is raised , so you can handle it and properly notify the user.
ReplyDeleteYou do not need to implement this yourself, Hibernate already supports it through the @Version annotation.
The "version approach" is a good idea. But is this not more related with data locking issue? What I am saying is that whatever persistence context we use, we should use the version approach.
ReplyDeleteAre you expert in data locking?
No, I'm no expert. But yes, versioning is closely related to data locking and it does make sense to use it whenever you are depending on optimistic locking, regardless the persistence context's scope. This is my interpretation, at least.
ReplyDelete