在JSF中使用JPA实体。如何最好地避免LazyInitializationException?

12 浏览
0 Comments

在JSF中使用JPA实体。如何最好地避免LazyInitializationException?

希望听到专家关于从JSF UI编辑JPA实体的最佳实践。所以,对问题做出一些解释。

想象一下,我有一个已持久化的对象 MyEntity ,我将它获取用于编辑。在DAO层中,我使用

 return em.find(MyEntity.class,id);

它返回带有“parent”实体的代理的 MyEntity 实例 - 想象其中之一是 MyParent MyParent 作为代理被获取以 @Access(AccessType.PROPERTY)

 @Entity
public class MyParent {
    @Id
    @Access(AccessType.PROPERTY)
    private Long id;
    // ...
}

而MyEntity对它有引用:

 @ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.PROXY)
private MyParent myParent;

到目前为止都好。在UI中,我直接使用获取的对象,而不创建任何值对象,并在选择列表中使用父对象:

 
    

一切都呈现正常,没有发生 LazyInitializationException 。但是,当我保存对象时,我收到以下错误消息:

 LazyInitializationException:无法初始化代理 - 无会话

MyParent 代理的 setId()方法上。

如果我将 MyParent 关系更改为 EAGER ,我可以轻松解决这个问题

 @ManyToOne(fetch = FetchType.EAGER)
private MyParent myParent;

或者使用 left join fetch p.myParent 来获取对象(实际上这就是我现在做的)。在这种情况下,保存操作正常工作,并且关系透明地更改为新的 MyParent 对象。不需要执行其他操作(手动复制,手动设置引用)。非常简单和方便。

但是 。如果对象引用其他10个对象 - em.find()将导致 10个额外的连接,这不是一个好的数据库操作,特别是当我根本不使用引用对象的状态时。我所需要的只是对象的链接,而不是它们的状态。

这是一个全局问题,我想知道JSF专家如何处理他们应用程序中的JPA实体,以避免额外的连接和 LazyInitializationException 。扩展持久性上下文对我来说不合适。

谢谢!

0
0 Comments

使用JPA实体在JSF中的常见方法是创建一个"open entity manager in view filter"。Spring提供了一个这样的类(参见此处)。尽管您没有使用Spring,但这并不是问题,您可以根据自己的需求调整该类中的代码。您还可以查看"Open Session in View"过滤器,它与Spring的实现相似,但它保持了一个Hibernate会话而不是一个实体管理器。

然而,这种方法可能不适用于您的应用程序,因为关于这种模式或反模式有一些讨论。对于大多数应用程序(小型应用,少于20个并发用户),这种解决方案应该是完全有效的。

在EJB容器中,我不知道如何做到这一点。我的过时的EJB知识告诉我,所有的实体在离开服务时应该被填充(通常是手动完成)或者它们应该转换成DTOs。

0
0 Comments

使用JPA实体在JSF中,防止LazyInitializationException的最佳策略是什么?

出现问题的原因:

如果我们有一个存储在Session中或由Session Bean处理或从缓存中检索的实体,并且在同一加载请求期间没有初始化其集合之一,那么无论我们稍后何时调用它,都可能随时出现异常,即使我们使用OSIV设计模式。

问题的详细情况:

任何Hibernate代理都需要附加到已打开的会话才能正常工作。

Hibernate没有提供任何工具(监听器或处理程序)来重新附加代理,以防止其会话关闭或从自身会话中分离。

Hibernate为什么不提供这个工具?

因为很难确定代理应该重新附加到哪个会话,但是在许多情况下我们可以确定。

那么在LazyInitializationException发生时如何重新附加代理?

在我的ERP中,我修改了这些类:JavassistLazyInitializer和AbstractPersistentCollection,然后我再也不关心这个异常(使用了三年没有任何错误):

class JavassistLazyInitializer{
    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 ) {
        /* 捕获LazyInitException并将代理重新附加到正确的会话 */
                EntityManager em = ContextConfig.getCurrent().getDAO(
                                    BaseBean.getWcx(), 
                                    HibernateProxyHelper.getClassWithoutInitializingProxy(proxy)).
                                    getEm();
                            ((Session)em.getDelegate()).refresh(proxy);// 附加代理                   
                }   
                try{                
                    if (target==null)
                        target = getImplementation();
                        .....
                }
    ....
 }

class AbstractPersistentCollection{
    private  T withTemporarySessionIfNeeded(LazyInitializationWork lazyInitializationWork) {
        SessionImplementor originalSession = null;
        boolean isTempSession = false;
        boolean isJTA = false;      
        if ( session == null ) {
            if ( allowLoadOutsideTransaction ) {
                session = openTemporarySessionForLoading();
                isTempSession = true;
            }
            else {
/* 让我们尝试将代理重新附加到正确的会话 */
                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" );
        ....
    }
...
}

注意:

我没有修复所有可能性,例如JTA或其他情况。

当您激活缓存时,此解决方案甚至更好。

0
0 Comments

通过阅读上述内容,我们可以得出以下结论:

出现LazyInitializationException异常的原因是由于在JSF中使用JPA实体时,实体可能会处于延迟加载状态,当访问延迟加载属性时,会抛出此异常。

为了解决这个问题,可以采取以下策略:

1. 如果JPA实体恰好与所需模型完全匹配,则可以直接使用该实体。

2. 如果JPA实体的属性过少或过多,则可以使用DTO(子类)和/或具有更具体JPQL查询的构造器表达式,如果必要,可以使用显式的FETCH JOIN。或者,可以使用Hibernate特定的fetch profiles,或者EclipseLink特定的属性组。否则,可能会导致在各个地方出现延迟初始化异常,或者消耗比必要更多的内存。

3. “open session in view”模式是一种设计不良的模式。在整个HTTP请求-响应处理期间,您基本上保持了单个数据库事务的开放状态。您被完全剥夺了启动新的数据库事务的控制权。当业务逻辑需要在同一HTTP请求中生成多个事务时,就无法实现。请记住,当事务中的单个查询失败时,整个事务将被回滚。

4. 在JSF的角度来看,“open session in view”模式还意味着在渲染响应时可以执行业务逻辑。这与异常处理等不太合适,因为意图是向最终用户显示自定义错误页面。如果在渲染响应的过程中抛出业务异常,最终用户已经接收到响应头和部分HTML,则服务器无法清除响应以显示漂亮的错误页面。此外,在getter方法中执行业务逻辑是JSF中不推荐的做法。

5. 在渲染响应阶段开始之前,通过管理Bean的操作/监听方法,使用通常的服务方法调用准备好视图所需的模型。例如,常见情况是手头有一个现有(非托管)的父实体,具有延迟加载的一对多子实体属性,您希望通过ajax操作在当前视图中渲染它,那么您只需让ajax监听方法在服务层中获取并初始化它即可。


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.
}

6. 特别是在JSF的UISelectMany组件中,可能会出现LazyInitializationException的另一个完全意想不到的可能原因是:在保存选中项时,JSF需要在填充选中项之前重新创建底层集合。如果底层集合是一种特定于持久化层的延迟加载集合实现,则也会抛出此异常。解决方法是将UISelectMany组件的collectionType属性显式设置为所需的“plain”类型。


以上就是解决LazyInitializationException异常的原因和方法,通过这些方法可以有效地预防LazyInitializationException异常的出现。

0