为什么调用 Domain.current().applications 会抛出 LazyInitializationException 异常?
李小翔
2013-11-13 09:00

内容列表

问题代码：
import bropen.framework.core.security.Domain;
// 获得当前管理域
Domain domain = Domain.current()
// 使用当前域下的所有应用进行查询，会抛出异常！！
ProcessDefinition.findAllNotDisabledByApplicationInList(domain.applications, [cache:true])
抛出异常：
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
    bropen.framework.core.security.Domain.applications, no session or session was closed
    ... 13 more
在错误日志上加上 withTransaction 或者 withNewSession 都不管用，该如何处理呢？
其原因在于，bropen.framework.core.security.Domain.current()（包括DomainApplication.current()）拿到的实体Bean都是来自缓存，而这些缓存可能是早在被其他代码（比如BootStrap）调用时生成的，当时的Hibernate Session早已关闭，如果通过这些Bean去获得关联的其他实体对象，肯定会抛出no session or session was closed异常。
知道原因后，解决问题就很容易了，可以采用两种方式来处理：
1. 通过获得实体对象后，调用 attach 方法将对象附加到当前的Hibernate Session中，代码如：
if ( !bean.isAttached() ) { 
    bean.attach()
} 
这种处理方式适用与多数类似的异常，但是如果这个bean可能同时被多个线程调用，这样做会不会导致线程不安全的情况发生呢（细心的读者可以验证一下）。
因此，比较偷懒的做法是采用下面的方案2来规避这个问题。
2. 接受这个bean游离在当前Hibernate Session之外的现实，通过其他手段来获取关联的其他实体对象，就最上面的问题代码来说，改造如下：
import bropen.framework.core.security.Domain;
import bropen.framework.core.security.DomainApplication;
// 从缓存中获得当前管理域对象
Domain domain = domain.current();
// 加一行，避免session关闭问题
List<DomainApplication> apps = DomainApplication.findAllByDomain(domain);
// 这回就不会异常了
ProcessDefinition.findAllNotDisabledByApplicationInList(apps, [cache:true]);
当然，为什么不把关联的 applications 也缓存下来呢？
呵呵，那缓存的工作量就比较大了，整个系统的数据结构都有可能相互关联，谁也无法预料到二次开发时中会如何调用，这样做意义并不大；而且，如果关联的对象发生了变化，还得通知缓存进行更新......
类似的，如果碰到类似的异常，也可以采用相同的方法处理。