Search code examples
javaspringhibernatesession

Why is the Hibernate Session still open after the transaction is committed


I was perplexed when the creation of a projection from a lazily loaded entity collection didn't throw a LazyInitializationException, when returning a DTO response from a stateless REST service's update method. So I checked, and the Hibernate Session was still open, even after the transaction has completed and was no longer active (as per the printout):

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@PutMapping(value = "/{employeeId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public EmployeeDto updateEmployee( @PathVariable Long employeeId, @Valid @RequestBody UpdateEmployeeDto updateEmployeeDto) {       
   Employee employee = employeeService.updateEmployee(employeeId, updateEmployeeDto);
   System.out.println("Transaction Active: " + 
   TransactionSynchronizationManager.isActualTransactionActive()); // prints false
   Session hibernateSession = entityManager.unwrap(Session.class);
   System.out.println("Hibernate Session Open: " + hibernateSession.isOpen()); //prints true
   return employeeMapper.toDto(employee);
}

I understood that by default, session management in Spring was tightly coupled with the transactional context, i.e. the session is scoped to the current database transaction. So why didn't it close after triggering the previous tx commit?

A bare @Transactional is used on the update employee method, and there is no extended persistence context applied.

I remember getting LazyInitExceptions when converting to DTO projections from previous projects, I must be missing something apparent, though there is no special configuration done to the transaction/session management of this project.


Solution

  • You are a victim of the OSIV anti-pattern.

    Vlad Mihalcea describes the problem very well here: What is this spring.jpa.open-in-view=true property in Spring Boot?

    TL;DR
    Spring Boot has the OSIV pattern enabled by default, which means that for every HTTP request, your database session is being bound by Spring to the HTTP session. In other words, your hibernate session stays open because the HTTP request is not yet finished at the time of the print.

    Disable the property:

    spring.jpa.open-in-view=false
    

    And then you should get a LazyInitException.

    I have also been a victim of this pattern: I was starting a Spring batch-job with an HTTP REQUEST, instead of a chron job, for testing purposes, and I wasted hours of analysing heap dumps just to see in the Eclipse Memory Analyzer Tool that the SessionImpl was creating a memory leak of GBs with StatefulPersistenceContext, because it wasn't closing. Thousands upon thousands of entities could not be garbage collected, because the SessionImpl (hibernate) was bound to the HTTP session, which of course for a long running job would not end soon.

    For me, the problem was that a single EntityManager was being used for all the batched transactions, as user dunni explains in his answer: "This property will register an OpenEntityManagerInViewInterceptor, which registers an EntityManager to the current thread, so you will have the same EntityManager until the web request is finished."