Spring+Hibernate+Envers+多线程-会话已关闭



我们使用Hibernate(带有JPA)和Hibernate Envers来持久化对象的历史。web应用程序运行许多线程,其中一些是由其他应用程序的RMI方法调用创建的,一些是由应用程序本身创建的,还有一些是为处理http请求而创建的(它们生成视图)。

我们还使用Open Session In View模式来管理会话,因此我们的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访问的,所有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.hubinate.SessionException异常:会话已关闭!在org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:129)在org.hibernate.internal.SessionImpl.createQuery(SessionImpl.java:1776)在org.hibernate.envers.tools.query.QueryBuilder.toQuery(QueryBuilder.java:226)在org.hibernate.envers.query.impl.AbstractAuditQuery.buildQuery(AbstractAuditQuery.java:92)在org.hibernate.envers.query.impl.EntitiesAtRevisionQuery.list(EntitiesAtRevisionQuery.java:108)在org.hibernate.envers.query.impl.AbstractAuditQuery.getSingleResult(AbstractAuditQuery.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,那么在非ui线程中就没有SessionException

事实证明,仅仅使loadByRevision具有事务性是不够的。如果该方法返回的对象包含延迟加载的持久包,则在loadByRevision方法范围之外访问它们会导致LazyInitializationException(没有会话)。

最终的解决方案是确保如果任何线程想要从数据库加载一些数据,那么所有的加载(获取对象和访问延迟加载的集合)都将在一个用@Transactional注释的方法中完成。

最新更新