我们使用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访问数据库,所有这些都有Spring注入的EntityManagers。
@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: 会议结束!在 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)?
答案 0 :(得分:2)
我们没有找到为什么非{ui threads方法调用EntityManagerFactory
上的方法有效但AuditReaderFactory
上的方法调用没有。无论如何,我们找到了解决问题的方法。
解决方案是使用@Transactional
注释方法。如果在调用AuditReaderFactory之前调用链中的任何方法标记为@Transactional
,则非ui线程中没有SessionException
。
事实证明,制作loadByRevision
交易是不够的。如果该方法返回的对象包含延迟加载的持久化包,则在loadByRevision
方法范围之外访问它们会导致LazyInitializationException
(没有会话)。
最后的解决方案是确保如果任何线程想要从数据库加载一些数据,所有加载(获取对象和访问延迟加载的集合)将在一个注释为@Transactional
的方法内完成。