Dirty checking and flushing

Hibernate · lesson 5 of 32 · 3 min read

Learn why one setter updates a row, and when the SQL is actually sent.

Open this lesson in the learning hub

Key points

  • Hibernate snapshots every entity as it was loaded. At flush time it compares, then writes an UPDATE for whatever changed.
  • So a setter on a managed entity is a database write. Calling save() afterwards changes nothing.
  • Flush is when the queued SQL is sent. Commit is when the transaction ends. Flush always happens before commit.
  • With the default AUTO flush mode Hibernate also flushes before a query whose result the pending changes could affect.
  • Statement order is up to Hibernate: at flush it groups by operation type, so your call order is not the SQL order.
  • Loading entities you only read wastes snapshot memory. Use a DTO projection or @Transactional(readOnly = true) on read paths.

Example

@Transactional
public void rename(Long id, String newTitle) {
    Book book = em.find(Book.class, id);
    book.setTitle(newTitle);
    // No save() call. At flush Hibernate compares the snapshot and sends:
    //   update book set title=?, published_on=? where id=?
}

@Transactional
public void needTheIdNow(Book book) {
    em.persist(book);
    em.flush();          // force the INSERT now, e.g. before a native query
    auditLog.record(book.getId());
    // still one transaction: a rollback undoes the flushed INSERT too
}

Change a managed entity and the UPDATE writes itself.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.