Spring + Hibernate + Envers + 多线程 - 会话关闭
-
21-12-2019 - |
题
我们使用 Hibernate(带有 JPA)和 Hibernate Envers 来保存对象的历史记录。Web应用程序运行许多线程,其中一些线程是由其他应用程序调用RMI方法创建的,其中一些是由应用程序本身创建的,还有一些是为了处理http请求而创建的(它们生成视图)。
我们还使用“在视图中打开会话”模式来管理会话,因此我们的 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注入的EntityManager。
@PersistenceContext
protected EntityManager em;
@PersistenceUnit
protected EntityManagerFactory emf;
在我们决定使用 Hibernate Envers 之前,一切都运行良好。当任何不是视图生成线程的线程运行代码来获取对象的旧版本时,会引发异常。
@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;
}
线程“Scheduler”org.hibernate.SessionException 中的异常:会议已结束!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 。 hibernate.envers.query.impl.abstractauditquery.getsingleresult(AbstrackAuditquery.java:110)(...)
当上面的代码由视图生成线程运行时,它可以正常工作。此外,DAO 中的非 envers 代码适用于每个线程。例如,下面的代码片段
@Override
public O load(Long id) {
final O find = em.find(getBaseClass(), id);
return find;
}
可以通过 RMI 线程运行而不会出现问题。
为什么非视图线程可以无异常地调用实体管理器上的方法,但不能将 Envers 的 AuditReaderFactory 与该实体管理器一起使用?我认为调用实体管理器上的方法可能会创建一个临时会话,但使用 Envers 时不会发生这种情况,是真的吗?
解决该问题的最佳方法是什么(以便可以从每个线程使用 AuditReaderFactory)?
解决方案
我们没有找到为什么在非ui线程中方法调用 EntityManagerFactory
有效,但方法调用 AuditReaderFactory
没有。无论如何,我们找到了解决问题的方法。
解决方案是用注释方法 @Transactional
. 。如果在调用 AuditReaderFactory 之前调用链中的任何方法被标记为 @Transactional
, 没有 SessionException
在非 UI 线程中。
事实证明,制作 loadByRevision
交易性还不够。如果该方法返回的对象包含延迟加载的持久包,则在外部访问它们 loadByRevision
方法作用域引起 LazyInitializationException
(没有会议)。
最终的解决方案是确保如果任何线程想要从数据库加载一些数据,所有加载(获取对象和访问延迟加载的集合)都将在一个注释为的方法内完成 @Transactional
.