Some of my learnings while working with Hibernate in a multi-threaded Spring Boot application.

Multiple sessions

When you start an @Async method, it runs in its own thread managed by Spring. This means that the session created by the calling method may not be shared with the async method. The async method might create its own session.

This behaviour also happens when you use @Transactional(Transactional.TxType.REQUIRES_NEW) when there is a transaction already. This creates a new session as Hibernate has to suspend the current transaction in the current session.

Session is a database session that’s usually one to one with a database connection.

In this multi-session scenario, there are few things you need to be aware of.

1. Un-proxied objects (new transaction)

class ServiceA {

  @Transaction
  public void fetch() {
    Entity entity = // fetched from DB
    serviceB.process(entity);
  }

}

class ServiceB {
  
  @Transactional(Transactional.TxType.REQUIRES_NEW)
  public void process(Entity entity) {
    LazyEntity lazy = entity.getLazyAssociation(); // <- this can throw an exception
  }

}

When you use REQUIRES_NEW, the previous transaction is suspended and a new transaction is initiated via a new session/connection. So if you try to query something on the entities fetched from the previous session, you may get this exception:

Hibernate: illegally attempted to associate a proxy with two open Sessions

The fix is to unproxy the proxied object using this utility method from Hibernate:

    LazyEntity lazy = Hibernate.unproxy(entity.lazyEntity, LazyEntity.class);

2. java.lang.IllegalStateException: Session/EntityManager is closed or this.session is null

class ServiceA {

  public void fetch() {
    Entity entity = // fetched from DB
    serviceB.process(entity);
  }

}

class ServiceB {
  
  @Async
  public void process(Entity entity) {
    LazyEntity lazy = entity.getLazyAssociation(); // <- this can throw an exception
  }

}

If you have fetched an entity in a session and pass that entity to be processed by an async method which is connected to a different session, Hibernate will throw an exception like this:

java.lang.IllegalStateException: Session/EntityManager is closed

or

this.session is null

This is happening because the session created by the original thread (the thread that called the async method) could have finished its processing and closed the session it owned.

The fix is to refetch the entity from this new method:

    LazyEntity lazy = repository.findById(entity.getId()).getLazyAssociation();

Leave a Reply

Your email address will not be published. Required fields are marked *

Designing REST APIs

November 20, 2022