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 MyEntity
and I fetch it for editing. In DAO layer I use
return em.find(MyEntity.class, id);
Which returns MyEntity
instance with proxies on "parent" entities - imagine one of them is MyParent
. MyParent
is fetched as the proxy greeting to @Access(AccessType.PROPERTY)
:
@Entity
public class MyParent {
@Id
@Access(AccessType.PROPERTY)
private Long id;
//...
}
and MyEntity has the reference to it:
@ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
private MyParent myParent;
So far so good. In UI I simply use the fetched object directly without any value objects created and use the parent object in the select list:
<h:selectOneMenu value="#{myEntity.myParent.id}" id="office">
<f:selectItems value="#{parents}"/>
</h:selectOneMenu>
Everything is rendered ok, no LazyInitializationException
occurs. But when I save the object I recieve the
LazyInitializationException: could not initialize proxy - no Session
on MyParent
proxy setId()
method.
I can easily fix the problem if I change the MyParent
relation to EAGER
@ManyToOne(fetch = FetchType.EAGER)
private MyParent myParent;
or fetch the object using left join fetch p.myParent
(actually that's how I do now). In this case the save operation works ok and the relation is changed to the new MyParent
object transparently. No additional actions (manual copies, manual references settings) need to be done. Very simple and convenient.
BUT. If the object references 10 other object - the em.find()
will result 10 additional joins, which isn't a good db operation, especially when I don't use references objects state at all. All I need - is links to objects, not their state.
This is a global issue, I would like to know, how JSF specialists deal with JPA entities in their applications, which is the best strategy to avoid both extra joins and LazyInitializationException
.
Extended persistence context isn't ok for me.
Thanks!
Indicates an attempt to access not-yet-fetched data outside of a session context. For example, when an uninitialized proxy or collection is accessed after the session was closed.
You should provide exactly the model the view expects.
If the JPA entity happens to match exactly the needed model, then just use it right away.
If the JPA entity happens to have too few or too much properties, then use a DTO (subclass) and/or a constructor expression with a more specific JPQL query, if necessary with an explicit FETCH JOIN
. Or perhaps with Hibernate specific fetch profiles, or EclipseLink specific attribute groups. Otherwise, it may either cause lazy initializtion exceptions over all place, or consume more memory than necessary.
The "open session in view" pattern is a poor design. You're basically keeping a single DB transaction open during the entire HTTP request-response processing. Control over whether to start a new DB transaction or not is completely taken away from you. You cannot spawn multiple transactions during the same HTTP request when the business logic requires so. Keep in mind that when a single query fails during a transaction, then the entire transaction is rolled back. See also When is it necessary or convenient to use Spring or EJB3 or all of them together?
In JSF perspective, the "open session in view" pattern also implies that it's possible to perform business logic while rendering the response. This doesn't go very well together with among others exception handling whereby the intent is to show a custom error page to the enduser. If a business exception is thrown halfway rendering the response, whereby the enduser has thus already received the response headers and a part of the HTML, then the server cannot clear out the response anymore in order to show a nice error page. Also, performing business logic in getter methods is a frowned upon practice in JSF as per Why JSF calls getters multiple times.
Just prepare exactly the model the view needs via usual service method calls in managed bean action/listener methods, before render response phase starts. For example, a common situation is having an existing (unmanaged) parent entity at hands with a lazy loaded one-to-many children property, and you'd like to render it in the current view via an ajax action, then you should just let the ajax listener method fetch and initialize it in the service layer.
<f:ajax listener="#{bean.showLazyChildren(parent)}" render="children" />
public void showLazyChildren(Parent parent) {
someParentService.fetchLazyChildren(parent);
}
public void fetchLazyChildren(Parent parent) {
parent.setLazyChildren(em.merge(parent).getLazyChildren()); // Becomes managed.
parent.getLazyChildren().size(); // Triggers lazy initialization.
}
Specifically in JSF UISelectMany
components, there's another, completely unexpected, probable cause for a LazyInitializationException
: during saving the selected items, JSF needs to recreate the underlying collection before filling it with the selected items, however if it happens to be a persistence layer specific lazy loaded collection implementation, then this exception will also be thrown. The solution is to explicitly set the collectionType
attribute of the UISelectMany
component to the desired "plain" type.
<h:selectManyCheckbox ... collectionType="java.util.ArrayList">
This is in detail asked and answered in org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel.
For Hibernate >= 4.1.6 read this https://stackoverflow.com/a/11913404/3252285
Using the OpenSessionInView Filter (Design pattern) is very usefull, but in my opinion it dosn't solve the problem completely, here's why :
If we have an Entity stored in Session or handled by a Session Bean or retrieved from the cache, and one of its collections has not been initialized during the same loading request, then we could get the Exception at any time we call it later, even if we use the OSIV desing pattern.
Lets detail the problem:
Listener or Handler
) to reatach the proxy in case his session is closed or he's detached from its own session.Why hibernate dosn't offer that ? : because its not easy to identify to which Session, the Proxy should be reatached, but in many cases we could.
So how to reattach the proxy when the LazyInitializationException happens ?.
In my ERP, i modify thoses Classes : JavassistLazyInitializer
and AbstractPersistentCollection
, then i never care about this Exception any more (used since 3 years without any bug) :
class JavassistLazyInitializer{
@Override
public Object invoke(
final Object proxy,
final Method thisMethod,
final Method proceed,
final Object[] args) throws Throwable {
if ( this.constructed ) {
Object result;
try {
result = this.invoke( thisMethod, args, proxy );
}
catch ( Throwable t ) {
throw new Exception( t.getCause() );
}
if ( result == INVOKE_IMPLEMENTATION ) {
Object target = null;
try{
target = getImplementation();
}catch ( LazyInitializationException lze ) {
/* Catching the LazyInitException and reatach the proxy to the right Session */
EntityManager em = ContextConfig.getCurrent().getDAO(
BaseBean.getWcx(),
HibernateProxyHelper.getClassWithoutInitializingProxy(proxy)).
getEm();
((Session)em.getDelegate()).refresh(proxy);// attaching the proxy
}
try{
if (target==null)
target = getImplementation();
.....
}
....
}
and the
class AbstractPersistentCollection{
private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
SessionImplementor originalSession = null;
boolean isTempSession = false;
boolean isJTA = false;
if ( session == null ) {
if ( allowLoadOutsideTransaction ) {
session = openTemporarySessionForLoading();
isTempSession = true;
}
else {
/* Let try to reatach the proxy to the right Session */
try{
session = ((SessionImplementor)ContextConfig.getCurrent().getDAO(
BaseBean.getWcx(), HibernateProxyHelper.getClassWithoutInitializingProxy(
owner)).getEm().getDelegate());
SessionFactoryImplementor impl = (SessionFactoryImplementor) ((SessionImpl)session).getSessionFactory();
((SessionImpl)session).getPersistenceContext().addUninitializedDetachedCollection(
impl.getCollectionPersister(role), this);
}catch(Exception e){
e.printStackTrace();
}
if (session==null)
throwLazyInitializationException( "could not initialize proxy - no Session" );
}
}
if (session==null)
throwLazyInitializationException( "could not initialize proxy - no Session" );
....
}
...
}
NB :
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With