我们使用 Hibernate(带有 JPA)和 Hibernate Envers 来保存对象的历史记录。 Web 应用程序运行许多线程,其中一些是由其他应用程序调用 RMI 方法创建的,一些是由应用程序本身创建的,还有一些是为处理 http 请求(它们生成 View )而创建的。
我们还使用 Open Session In View 模式来管理 session ,因此我们的 web.xml 包含:
<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
数据库是使用 DAO 访问的,它们都具有由 Spring 注入(inject)的 EntityManagers。
@PersistenceContext
protected EntityManager em;
@PersistenceUnit
protected EntityManagerFactory emf;
在我们决定使用 Hibernate Envers 之前,一切都运行良好。当任何不是 View 生成线程的线程运行代码以获取对象的旧版本时,将抛出异常。
@Override
public O loadByRevision(Long revision, Long id) {
@SuppressWarnings("unchecked")
O object = (O) AuditReaderFactory.get(em).createQuery().forEntitiesAtRevision(getBaseClass(), revision.intValue())
.add(AuditEntity.id().eq(id)).getSingleResult();
return object;
}
Exception in thread "Scheduler" org.hibernate.SessionException: Session is closed! at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:129) at org.hibernate.internal.SessionImpl.createQuery(SessionImpl.java:1776) at org.hibernate.envers.tools.query.QueryBuilder.toQuery(QueryBuilder.java:226) at org.hibernate.envers.query.impl.AbstractAuditQuery.buildQuery(AbstractAuditQuery.java:92) at org.hibernate.envers.query.impl.EntitiesAtRevisionQuery.list(EntitiesAtRevisionQuery.java:108) at org.hibernate.envers.query.impl.AbstractAuditQuery.getSingleResult(AbstractAuditQuery.java:110) (...)
当上面的代码由 View 生成线程运行时,它工作正常。此外,DAO 中的非 envers 代码适用于每个线程。例如,下面的片段
@Override
public O load(Long id) {
final O find = em.find(getBaseClass(), id);
return find;
}
可以由 RMI 线程毫无问题地运行。
为什么非 View 线程可以毫无异常(exception)地调用实体管理器上的方法,但不能将 Envers 的 AuditReaderFactory 与该实体管理器一起使用?我认为也许在实体管理器上调用一个方法会创建一个临时 session ,但在使用 Envers 时不会发生这种情况,是这样吗?
解决该问题的最佳方法是什么(以便每个线程都可以使用 AuditReaderFactory)?
最佳答案
我们没有发现为什么在非 ui 线程中对 EntityManagerFactory
的方法调用有效,但对 AuditReaderFactory
的方法调用却没有。不管怎样,我们找到了修复它的方法。
解决方案是使用 @Transactional
注释方法。如果在调用 AuditReaderFactory 之前调用链中的任何方法被标记为 @Transactional
,则非 ui 线程中没有 SessionException
。
事实证明,使 loadByRevision
具有事务性是不够的。如果该方法返回的对象包含延迟加载的持久包,则在 loadByRevision
方法范围之外访问它们会导致 LazyInitializationException
(没有 session )。
最终的解决方案是确保如果任何线程想要从数据库加载一些数据,所有加载(获取对象和访问延迟加载的集合)都将在一个用 @Transactional 注释的方法中完成
。
关于java - Spring + Hibernate + Envers + 多线程 - session 关闭,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21023989/