Would like to hear experts on best practice of editing JPA entities from JSF UI.
So, a couple of words about the problem.
Imagine I have the persisted object
Lazy Loading is an important feature that can boost performance nicely. However the usability of this is way worse than it should be.
Especially when you start to deal with AJAX-Requests, encountering uninitialized collections, the Annotation ist just usefull to tell Hibernate don't load this right away. Hibernate is not taking care of anything else, but will throw a LazyInitializationException at you - as you experienced.
My solution to this - which might be not perfect or a nightmare over all - works in any scenario, by applying the following rules (I have to admit, that this was written at the very beginning, but works ever since):
Every Entity that is using fetch = FetchType.LAZY has to extend LazyEntity, and call initializeCollection() in the getter of the collection in question, before it is returned. (A custom validator is taking care of this constraints, reporting missing extensions and/or calls to initializeCollection)
Example-Class (User, which has groups loaded lazy):
public class User extends LazyEntity{
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@BatchSize(size = 5)
List groups;
public List getGroups(){
initializeCollection(this.groups);
return this.groups;
}
}
Where the implementation of initializeCollection(Collection collection) looks like the following. The In-Line comments should give you an idea of what is required for which scenario. The method is synchronized to avoid 2 active sessions transfering ownership of an entity while another session is currently fetching data. (Only appears when concurrent Ajax-Requests are going on on the same instance.)
public abstract class LazyEntity {
@SuppressWarnings("rawtypes")
protected synchronized void initializeCollection(Collection collection) {
if (collection instanceof AbstractPersistentCollection) {
//Already loaded?
if (!Hibernate.isInitialized(collection)) {
AbstractPersistentCollection ps = (AbstractPersistentCollection) collection;
//Is current Session closed? Then this is an ajax call, need new session!
//Else, Hibernate will know what to do.
if (ps.getSession() == null) {
//get an OPEN em. This needs to be handled according to your application.
EntityManager em = ContextHelper.getBean(ServiceProvider.class).getEntityManager();
//get any Session to obtain SessionFactory
Session anySession = em.unwrap(Session.class);
SessionFactory sf = anySession.getSessionFactory();
//get a new session
Session newSession = sf.openSession();
//move "this" to the new session.
newSession.update(this);
//let hibernate do its work on the current session.
Hibernate.initialize(collection);
//done, we can abandon the "new Session".
newSession.close();
}
}
}
}
}
But be aware, that this approach needs you to validate IF an Entity is associated to the CURRENT session, whenever you save it - else you have to move the whole Object-Tree to the current session again before calling merge().