BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Persistence and JPA

Persistence

Persistence context, flush and dirty checking

Inside a transaction, JPA keeps every entity it has loaded in a first-level cache and watches it for changes.

Learn these first

At a glance

Mental model

A clerk who photocopies every file you borrow.

What happens, in order
  1. 1Entities are in one of four states: new (transient), managed (attached to the context), detached (context closed) and removedpersist makes a new entity managed, merge copies a detached one onto a managed one.
  2. 2Loading the same id twice in one transaction returns the same Java object (identity guarantee)That is the first-level cache.
  3. 3At flush (before a query, before commit, or on flush()), Hibernate compares managed entities with their snapshots (dirty checking) and issues INSERT / UPDATE / DELETE
  4. 4After the transaction ends, entities are detachedTouching a lazy association then throws LazyInitializationException.
Remember
Watch out
Expecting lazy loading after the transaction endedLoading thousands of entities in one transactionUsing merge with a half-filled object, which overwrites columns with nullRelying on open-in-view and never noticing N+1
Example (highlighted lines explain it)
@Transactionalpublic void rename(long id, String name) {    Customer c = em.find(Customer.class, id);    c.setName(name);    // no save call needed: the change is detected and written at flush time}@Transactionalpublic void importAll(List<Customer> batch) {    int n = 0;    for (Customer c : batch) {        em.persist(c);        if (++n % 200 == 0) {            em.flush();            em.clear();        }    }}
  1. line 6 no save call needed: the change is detected and written at flush time
  2. line 12 uses persist, the core of Persistence context, flush and dirty checking
  3. line 14 uses flush, the core of Persistence context, flush and dirty checking
  4. line 15 uses clear, the core of Persistence context, flush and dirty checking
Read the full explanation (about 2 min)

Inside a transaction, JPA keeps every entity it has loaded in a first-level cache and watches it for changes. Changed fields are written automatically at flush time, even if you never call save.

A mental model

A clerk who photocopies every file you borrow. At closing time she compares each file with its copy and updates the archive for anything you scribbled on, without being asked.

How it works

Entities are in one of four states: new (transient), managed (attached to the context), detached (context closed) and removed. persist makes a new entity managed, merge copies a detached one onto a managed one.

Loading the same id twice in one transaction returns the same Java object (identity guarantee). That is the first-level cache.

At flush (before a query, before commit, or on flush()), Hibernate compares managed entities with their snapshots (dirty checking) and issues INSERT / UPDATE / DELETE.

After the transaction ends, entities are detached. Touching a lazy association then throws LazyInitializationException.

Going deeper

Calling setters on a managed entity inside a transaction saves it. repository.save(entity) is then redundant, and forgetting this explains "I did not save it but the database changed".

Long loops that load many entities keep all of them (and their snapshots) in memory. Batch jobs should flush() and clear() every few hundred items, or use a stateless session or a bulk query.

The "Open Session in View" setting keeps the context open during rendering so lazy loading works in controllers, hiding N+1 queries. Many teams turn it off (spring.jpa.open-in-view=false) and fetch what they need in the service.

Under the hood

The Session (Hibernate) or EntityManager holds a map from entity key to instance and a second map of loaded state snapshots. Flush walks the map, produces an ActionQueue of insert, update and delete actions, orders them, and executes them as JDBC batches. Detach just drops the entry from the maps.

Common mistakes

  • Expecting lazy loading after the transaction ended.
  • Loading thousands of entities in one transaction.
  • Using merge with a half-filled object, which overwrites columns with null.
  • Relying on open-in-view and never noticing N+1.

How Unravel recognises it

When you paste code, Unravel looks for these signs of persistence context, flush and dirty checking. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Persistence context, flush and dirty checking?

Inside a transaction, JPA keeps every entity it has loaded in a first-level cache and watches it for changes. Changed fields are written automatically at flush time, even if you never call save.

What are the most common mistakes with Persistence context, flush and dirty checking?

Expecting lazy loading after the transaction ended. Loading thousands of entities in one transaction. Using merge with a half-filled object, which overwrites columns with null. Relying on open-in-view and never noticing N+1.

How can I see Persistence context, flush and dirty checking in real code?

Paste your own Java into Unravel and it will point out where persistence context, flush and dirty checking appears, explain how it works at the detail level you choose, and draw the flow of the code around it.