java - Spring + Hibernate + Envers + 多线程 - session 关闭

标签 java spring hibernate jpa hibernate-envers

我们使用 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/

相关文章:

java - Spring Boot - 访问资源文件夹内的文件

java - 我可以一次插入多行吗?

java - 无法加载类 [org.postgresql.Driver]

Spring - 如何销毁原型(prototype)范围的 bean?

java - 部署 jar 文件并包含外部库

java - 使用反射——如何获取所有字段和方法

Java无法获取数组列表来打印?

spring - 加载部分 Spring 上下文

java - Hibernate 持久对象行为

java - 为什么构造函数调用在下面的例子中没有歧义?