Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring + Hibernate + Envers + multithreading - session is closed

We use Hibernate (with JPA) and Hibernate Envers to persist history of objects. The web application runs many threads, some of them are created by RMI method invocation from other applications, some of them are created by the application itself and some of them are created to handle http requests (they generate views).

We also use the Open Session In View pattern to manage sessions, so our web.xml contains:

<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>

Database is accessed using DAOs, all of them have EntityManagers injected by Spring.

@PersistenceContext
protected EntityManager em;

@PersistenceUnit
protected EntityManagerFactory emf;

Everything worked quite well before we decided to use Hibernate Envers. When any thread that is not a view-generating thread runs the code to get an old version of an object, the exception is thrown.

@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) (...)

When the code above is run by the view-generating thread it works fine. Also, the non-envers code in DAO works fine for every thread. For example, the snippet below

@Override
public O load(Long id) {
    final O find = em.find(getBaseClass(), id);
    return find;
}

can be run by RMI threads without problems.

Why can non-view threads call methods on the entity manager without exceptions but cannot use Envers' AuditReaderFactory with that entity manager? I thought that maybe calling a method on the entity manager creates a temporary session but that doesn't happen when using Envers, is that true?

What is the best way to fix that issue (so that the AuditReaderFactory can be used from every thread)?

like image 391
Paweł Chorążyk Avatar asked Jan 09 '14 15:01

Paweł Chorążyk


1 Answers

We didn't find out why in non-ui threads method calls on EntityManagerFactory worked but method calls on AuditReaderFactory didn't. Anyway, we found a way to fix it.

The solution was to annotate methods with @Transactional. If any method in the call chain before call to the AuditReaderFactory was marked as @Transactional, there was no SessionException in non-ui threads.

It turned out that making loadByRevision transactional was not sufficient. If an object returned by that method contained lazy-loaded persistent bags, access to them outside the loadByRevision method scope caused LazyInitializationException (there was no session).

The final solution was to make sure that if any thread wants to load some data from the database, all the loading (getting an object and accessing lazy-loaded collections) will be done inside one method annotated with @Transactional.

like image 83
Paweł Chorążyk Avatar answered Nov 03 '22 10:11

Paweł Chorążyk