Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › JPA › Persistence context

Persistence context

When does JPA send SQL?

JPA keeps entities in an in-memory space called the persistence context. It does not send SQL at the moment a line of code runs. It collects the SQL and sends it when it is needed.

Checked against real execution · The SQL of 6 scenarios was checked against Hibernate 7.4.5 and H2

Concepts in this lab

Persistence context
The logical space in JPA that holds and manages entities. Entities that you load or save are kept here, and JPA tracks the changes made to them and applies those changes to the database when the transaction ends. It usually shares its lifecycle with one transaction (in a web request with Open Session in View (OSIV) turned on, it lasts until the request ends). Features such as the first-level cache, dirty checking, and transactional write-behind work on top of it.
First-level cache
The store inside the persistence context that keeps entities by identifier. If you load the same identifier again within the same transaction, it returns the stored entity without sending a query to the database. As a result, entities with the same identifier are always the same object within the same transaction. By default it disappears when the transaction ends (in a web request with Open Session in View (OSIV) turned on, it lasts until the request ends), so it is different from an application-wide cache.
Dirty checking
The feature that automatically runs UPDATE statements for changed entities. When the values of an entity managed by the persistence context change, JPA compares the entity with the state it first read at flush time (usually just before the commit), and it runs an UPDATE statement for each entity whose values differ. By default, Hibernate puts all columns in the UPDATE statement, not only the changed columns. To include only the changed columns, use @DynamicUpdate. So if you only change the values of an entity that you loaded, the change is applied to the database without calling a separate save method.
Transactional write-behind and flush
Transactional write-behind means that INSERT, UPDATE, and DELETE statements are not sent to the database right away. They are collected and sent together. However, for an entity whose primary key is generated with the IDENTITY strategy (such as AUTO_INCREMENT in MySQL), the INSERT runs immediately when you request the save, because the identifier must be obtained. A flush sends the collected changes to the database. It happens automatically just before the transaction commits and just before a JPQL query runs. A flush only sends SQL to the database. It does not commit the transaction.
Entity
A Java object that corresponds to a row of a database table. It is defined as a class annotated with @Entity and has an identifier (@Id). JPA tracks the state changes of these objects and runs the matching SQL.

Try these

Running with the default settings

  1. Line 1: A SELECT reads member 1 and keeps it in the persistence context. The value at the time it was read, "Alice", is kept as the snapshot.
  2. Line 2: Only the name of the Java object was changed. No SQL goes to the database at this moment. Inside the persistence context, the current value (Bob) now differs from the snapshot (Alice).
  3. Line 3: The method ends without calling save.
  4. Method ends, commit: A flush happens just before the commit. JPA compares the current values of the managed entities with their snapshots, finds that member 1 has changed, builds an UPDATE, sends it to the database, and then commits. This is dirty checking.

Result of this run

Code

@Transactional
public void run() {
    Member m = memberRepository.findById(1L).orElseThrow();
    m.setName("Bob");
    // The method ends without calling save()
}   // the commit happens when the method ends

Assumptions of this model

This lab assumes that the code runs inside a single persistence context. For the sequence strategy, it simplifies the model to one sequence query per persist call. With the actual default setting (allocationSize 50), ids are fetched in blocks, so far fewer sequence queries are sent. In the "Without @Transactional" example, it assumes that the query method of a Spring Data repository runs in its own transaction and then ends. With the default open-in-view setting of Spring Boot, the entity may remain in the managed state, but there is still no commit, so no UPDATE is sent and the result is the same. All 6 scenarios were run on real Hibernate 7.4.5 with an H2 database, and the number of SQL statements sent, the time at which the write SQL was sent, and the values left in the DB were confirmed to match the screen (for the sequence strategy, they were run with allocationSize set to 1 to match the simplification on the screen).

Previous lab: What JPA solves · Next lab: The lifecycle of an entity

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these