Understanding JPA Session Boundaries and Transactions
Saeed Anabtawi
Founder @ interview.ps | Mentor @GazaSkyGeeks | Content Creator @ CodeWithSaeed
Understanding the distinction between transaction boundaries and JPA session boundaries is crucial for efficient data management and performance optimization in Spring JPA.
Transaction Boundaries
Transactions are fundamental to ensuring data consistency and integrity. A transaction boundary defines the start and end of a series of operations that must either be completed successfully as a unit or be entirely rolled back. In Spring, transaction management can be declarative using annotations @Transactional or programmatic using transaction managers. Key aspects include:
JPA Session Boundaries
A JPA session, often referred to as an EntityManager, is responsible for managing the lifecycle of entity instances and handling interactions with the database. The session boundary defines the scope within which entities are managed. Key considerations include:
Out-of-the-box Behavior (Spring Boot)
By default, a JPA session opens when a transaction block begins. Suppose you have a JPA entity named Student and a corresponding repository, StudentRepository, which is based on CrudRepository<Student, Long> from Spring Repository. What occurs when you execute the findById method? What happens when you execute the save method?
The transaction (Tx) is opened and closed solely within the findById method. It does not extend beyond the default findById method. As previously mentioned, the JPA session opens at the first encounter of a transaction, meaning it begins simultaneously with the transaction. However, in Spring Boot, the JPA session, by default, remains open until the final response is delivered to the client. This approach is known as Open Session In View
Keeping the session open after the transaction block closes allows for lazy loading of entities outside the transaction block. Therefore, if a lazily loaded relationship wasn't explicitly fetched during findById and you attempt to access it within StudentService, it would be lazily loaded without any issues.
Enter Open Session In View (OSIV)
Open Session In View (OSIV) is a strategy that keeps the session open until the HttpResponse is committed. This is implemented through a filter, and many consider OSIV to be an anti-pattern.
Because OSIV is implemented using a ServletFilter, it only works with HTTP requests. If a transaction or JPA session is initiated by other means, such as consuming a Kafka message, OSIV is not effective.
The history of OSIV dates back to when most Spring services used view rendering, such as JSP, instead of returning JSON for REST API responses. When many relationships are lazily loaded, having the session open during view rendering is convenient to avoid the dreaded. LazyInitializationException.
In Spring Boot, OSIV is enabled by default, which is controversial. You should check references about whether to log a warning when using this default behavior. In the vanilla Spring Framework, it is not enabled by default.
OSIV can be configured via spring.jpa.open-in-view=true/false. Refer to the documentation for more details.
If OSIV were disabled in the example above, the JPA session boundary would be the same as the transaction block
Working with Attached Entities outside of Tx Block
As mentioned earlier, you can lazily load relationships with attached entities. An entity is considered 'attached' if its JPA session is still open (more on this later). However, any modifications to the entity will not be automatically persisted. This means you must manually persist (save) the entity if it is modified
领英推荐
If you are implementing a REST API service without any view rendering and have a @Transactional block at the service boundary where lazy loading can occur, it is best practice to disable OSIV. Maciej Walkowiak explains this in more depth in his video
Working with Attached Entities inside of Tx Block
What happens if you annotate StudentService with @Transactional? Let's assume OSIV is disabled, meaning the transaction boundary is the same as the session boundary.
As expected, you can lazily load relationships within the boundary. Additionally, any modifications to the attached entities are automatically synchronized with the database when you exit the transaction block. This can sometimes confuse those accustomed to manually calling save for everything. But what if you need to modify values without having the transaction automatically sync the data? Let's take a quick look at the JPA session lifecycle.
Remember that in Spring Boot, OSIV is enabled by default, so if it is not explicitly disabled in the configuration, the diagram will look like the following.
JPA Session Lifecycle
When you modify persistent (also called managed or attached) entities within a transaction block, they will be automatically 'flushed' upon exiting the block, meaning any changes will persist. In Spring, the JPA session is represented by the EntityManager (also known as the persistent context), which tracks changes to attached entities as 'dirty' so it knows which entities to flush to the database.
If you want to ensure that modifications do not persist in the database, you can manually 'detach' an entity. In Spring, this must be done manually.
@Repository
public class StudentCustomRepository {
@PersistenceContext
private EntityManager entityManager;
public void detachEntity(Student student) {
entityManager.detach(Student)
}
}
Best Practices
By carefully managing transaction and session boundaries in Spring JPA, developers can create robust, efficient, and scalable data access layers that adhere to the ACID principles and optimize resource utilization. Understanding these concepts is essential for building high-performance applications with Spring and JPA.
References