Photo by Glenn Carstens-Peters on Unsplash
Two reasons why you might want to disable Open Session in View in a Spring application
The Open Session in View pattern can lead to performance issues and surprising errors in Spring Boot. Maybe you want disable it.
Open Session in View is a mechanism in Spring Boot which opens a Hibernate Session
when processing a HTTP Request in @RestController
and closes it when the processing of the request is over.
The advantage of this mechanism is that it allows you to traverse properties of JPA Entities that are marked as lazy loaded, and load them when you need them without problems. This creates a good developer experience as lazy-loading works as you’d expect it. Without this mechanism you’d get LazyInitilizationException
s when trying to access lazy loaded attributes.
There are however two problems in encountered recently with it: performance and correctness.
Performance
A part of the system needed to fetch data from external HTTP REST APIs. With OSIV enabled by default a database transaction and accordingly a database connection was opened for the whole time of processing the request. Sometimes the calls to the external API took multiple seconds. This way a dozen concurrent user quickly used up all the database connections available from the connection pool and the database server. The system got slower and eventually run out of database connections.
Hibernate Session = Database Transaction = Database Connection
A good tool to measure how long a connection was leased from the connection pool is FlexyPool. The easiest way to use it in a Spring Boot application is through a DataSource decorator which integrates with Spring Boot’s autoconfiguration mechanism: https://github.com/gavlyukovskiy/spring-boot-data-source-decorator
This way the only thing needed to use FlexyPool is to add the dependency to the POM file:
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>flexy-pool-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
After that you’ll see in the logs output like this:
2024-11-25 22:55:24.656 INFO 19304 --- [nio-8080-exec-3] c.v.flexypool.FlexyPoolDataSource : Connection acquired in 0 millis, while threshold is set to -1 in dataSource FlexyPoolDataSource
... stuff happening here with the call to the external API
2024-11-25 22:55:26.676 INFO 19304 --- [nio-8080-exec-3] c.v.flexypool.FlexyPoolDataSource : Connection leased for 2319 millis, while threshold is set to 0 in dataSource FlexyPoolDataSource
In the above logs you can see that a connection was leased for over 2300ms which is really a lot and it should be looked into how this the can be lowered so that a connection can be given back to the pool.
Correctness
The system i’m working on is a backend-heavy system with a little UI. A business decision caused the UI part to be reduced further in favor of automatic processing through jobs. As Spring Boot is used in this project the jobs are started using the @Scheduled
annotation.
Code which previously was triggered by the JavaScript GUI and @RestControllers
was now being executed time based by that Spring Boot backend itself. And problems started to arise…
Code which worked previously flawlessly now threw errors when accessing fields marked with FetchType.LAZY
- namely LazyInitilizationException
s. The project had Open Session In View enabled (this is the default in Spring Boot) and lazy loading worked as expected when the GUI called the REST Endpoints. No one thought that the same code now executed by jobs would not work. But that was the case now.
JPQL Queries had to be modified and added JOIN FETCH
clauses to load relations immediately. Some queries were replaced by Projections. All in all a lot of work was involved in adapting the code and testing it.
Testing
Given the following Entites Employee
and Department
we want first to fetch the Employee and then display the name of his Department. Note that in the Employee
Entity the Department
is marked with the FetchType.LAZY
.
import javax.persistence.*;
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id")
Department department;
public Department getDepartment() {
return department;
}
//more code ommitted for brevity
}
import javax.persistence.*;
@Entity
@Table(name = "department")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column
String deptName;
public String getDeptName() {
return deptName;
}
}
Now getting the deptName
like below in @GetMapping
will work correctly. The Department will be lazily loaded, with a second SQL Query and the name will be displayed.
However the same code run in a @Scheduled
method will throw a LazyInitializationException
.
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
private EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@GetMapping(path = "test")
@ResponseBody
ResponseEntity<Employee> testOsiv(){
// Hibernate session is opened
Employee emp = employeeService.findById(1);
String deptName = emp.getDepartment().getDeptName();
System.out.println(deptName);
return ResponseEntity.ok().build();
// Hibernate session is closed
}
@Scheduled(cron = "0 0 17 * * MON-FRI")
public void testNotOsiv() {
// Hibernate Session is opened in the repository
Employee emp = employeeService.findById(1);
// Hibernate session is closed
String deptName = emp.getDepartment().getDeptName(); //⚡ LazyInitializationException
System.out.println(deptName);
}
}
[Hibernate]
select
employee0_.id as id1_1_0_,
employee0_.age as age2_1_0_,
employee0_.dept_id as dept_id6_1_0_,
employee0_.first_name as first_na3_1_0_,
employee0_.last_name as last_nam4_1_0_,
employee0_.level as level5_1_0_
from
employee employee0_
where
employee0_.id=?
2024-10-18 22:45:28.704 ERROR 10744 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.hibernate.LazyInitializationException: could not initialize proxy [com.example.demo27.employee.Department#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
What to do?
You have to know that Open Session in View is enabled in Spring Boot by default. You can look at the logs when the app starts that indicate this.
spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
You can disable Open-Session-in-View setting spring.jpa.open-in-view
to false
.
The best way is to make a decision at the beginning of the project and stick to it. Because changing that decision later is not easy.
If you decide to switch OSIV off you’ll need to adapt code to fetch the data you need without relaying on lazy loading working.
If you decide to switch OSIV on you can also run into problems becuase now you’ll have one Hibernate session for the entire request and you might get the performance problems mentioned above as well as issues with things like reattaching of persistent objects to a Hibernate Session.
My personal choice is to disable it at the start of the project and use JPQL Queries per Use Case with JOIN FETCH
, EntityGraphs or Projections to avoid the LazyInitializationException.
There is an epic discussion on GitHub which outlines why it’s not so easy to decide. You get the developer expierience on one side and potential errors and performance degradation on the other side.
Docs: