Dev Learning Lab

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

Learn › JPA › Persistence context

The lifecycle of an entity

Why is the same setName sometimes not applied to the DB?

An entity object is in one of four states: new, managed, detached, or removed (Hibernate calls the new state transient). JPA watches the changes of values and applies them to the DB only when the object is in the managed state.

Checked against real execution · 14 combinations were run on Hibernate 7.4.5 and H2, and even the SQL sent on each line was checked against the results

Concepts in this lab

The four entity states
The classification of how an entity object relates to the persistence context, also called the entity lifecycle. New means that the object was only created with new and JPA does not know about it (Hibernate calls this state transient). Managed means that the persistence context manages the object, because you handed it over with em.persist or loaded it. Detached means that the object was managed and then came off. Removed means that the object was made a target to delete with em.remove. Features such as dirty checking, where changing a value sends an UPDATE, and the first-level cache work only for entities in the managed state. So when a value is not applied to the DB, first check which state the object is in now.
Detached entity
The state of an entity that the persistence context managed but no longer manages. When you detach an entity with em.detach, empty the persistence context with em.clear, or close it with em.close, the entities that were in it become detached. A detached entity still has its identifier (id) value, but it is like an ordinary Java object, so even if you change its values, no UPDATE is sent and no exception is thrown. An object created with new from the values received from a screen is also treated as detached if it has the id of a row that already exists in the DB. You often meet it in code that modifies an entity after the transaction has ended.
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.
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.

Try these

Running with the default settings

  1. em.find(Member.class, 1L): A SELECT reads member 1 and keeps it in the persistence context. From now on, m is in the managed state. The persistence context remembers the value it first read (Alice) and watches m for changes.
  2. em.detach(m): Takes m out of the persistence context. m changed from managed to detached. No SQL is sent.
  3. m.setName("Bob"): Changed the name of a detached object. The value of the Java object changes, but the persistence context no longer watches this object. No error occurs either.
  4. em.contains(m) is false. It means that the persistence context no longer manages m.
  5. commit: A flush happens just before the commit, but m is not managed, so it is not compared. No UPDATE is sent, and the name in the DB is still "Alice".

Result of this run

Code

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Member m = em.find(Member.class, 1L);
em.detach(m);
m.setName("Bob");
System.out.println(em.contains(m));
em.getTransaction().commit();

Assumptions of this model

All 14 combinations (7 paths, each with two choices for when the name is changed) were run on real Hibernate 7.4.5 and an H2 database, and the kind of SQL sent on each line, the result of em.contains, and the rows left in the DB were confirmed to match the screen. The setting "When the name is changed" is hidden for the paths where it does not affect the result: only new, load only, and remove. The following facts stated in the summaries were also run separately and confirmed: if you call em.flush() after changing a value and then detach, the change is applied; if you load the same id again after detaching, the SELECT is sent again and a different object comes back; if you call persist again on a removed object before the commit, no DELETE is sent; the values of the Java object can still be read after it was deleted; if you change a value after persist, the INSERT contains the value from the time of persist. The id of the member is set directly in the code. With the IDENTITY strategy, where the DB decides the id, the INSERT is sent right on the persist line. The SQL statements on the screen are shortened to be easy to read. In Spring, the begin, commit, and close here happen on your behalf when a @Transactional method starts and ends (in a web request with Open Session in View (OSIV) turned on, em is closed when the request ends).

Previous lab: Persistence context · Next lab: Detached entities and merge

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these