Home/Learn/Hibernate & JPA/Dirty Checking

Dirty Checking

Intermediate
Performance

Hibernate tracks managed entity snapshots; on flush it compares current state with the snapshot and issues UPDATE for changed fields — understand this to avoid unexpected SQL statements.

Overview

Dirty checking is Hibernate's automatic change detection mechanism. When you load an entity into a managed state (via `find()`, JPQL, or Spring Data repository), Hibernate stores a **snapshot** of its field values at that moment. When the persistence context is **flushed** (before a query that reads the same table, on `@Transactional` method return, or explicitly via `em.flush()`), Hibernate compares every managed entity's current state against its snapshot. Any field that differs is considered "dirty" and Hibernate generates an `UPDATE` statement for it. This is entirely automatic — you never need to call `save()` on a managed entity. The downside: for large entity graphs, comparing thousands of fields on every flush is expensive. Optimise with `@DynamicUpdate`, `@Immutable`, or read-only sessions.

Automatic Dirty Tracking in Action

Simply mutate a managed entity's fields within a `@Transactional` method — Hibernate automatically detects the change and issues an UPDATE on flush. No explicit `save()` call is needed for managed entities. Calling `save()` on an already-managed entity is a no-op (it just returns the same instance). Only detached entities (outside a transaction) require `merge()` or `save()`.

Hibernate — automatic dirty detection in @Transactional
@Transactional
public void updatePrice(Long productId, BigDecimal newPrice) {
    // Loaded entity is NOW MANAGED — Hibernate holds a snapshot
    Product product = productRepo.findById(productId).orElseThrow();

    // Mutate the field — Hibernate detects this as "dirty"
    product.setPrice(newPrice);
    product.setUpdatedAt(Instant.now());

    // NO explicit save() needed!
    // On @Transactional method exit:
    //   Hibernate flushes → compares current state to snapshot
    //   → snapshot.price != current.price → generates:
    //   UPDATE products SET price=?, updated_at=? WHERE id=?
}

// Pitfall: mutating outside a transaction
Product p = productRepo.findById(1L).orElseThrow();  // detached after method returns
p.setPrice(BigDecimal.TEN);   // change is LOST — no persistence context
// Must call productRepo.save(p) to re-attach via merge

@DynamicUpdate and Selective Column Updates

By default, Hibernate's UPDATE statement includes ALL columns — even unchanged ones. This wastes DB resources and can cause issues with optimistic locking or generated columns. `@DynamicUpdate` tells Hibernate to include only dirty (changed) columns in the UPDATE statement. This is especially useful for wide entities (many columns) where only a few change per request.

Hibernate — @DynamicUpdate for selective column updates
@Entity
@DynamicUpdate           // UPDATE includes only changed columns
@Table(name = "products")
public class Product {
    @Id Long id;
    String name;
    BigDecimal price;
    String description;   // 5000-char text — don't include unless changed
    String category;
    boolean active;
    Instant updatedAt;
}

// Without @DynamicUpdate:
// UPDATE products SET name=?, price=?, description=?, category=?,
//                     active=?, updated_at=? WHERE id=?
// (all 6 columns every time)

// With @DynamicUpdate:
// UPDATE products SET price=?, updated_at=? WHERE id=?
// (only the 2 changed columns)

// Also useful: @DynamicInsert — only includes non-null columns in INSERT
@Entity
@DynamicInsert
public class Order { ... }

Disabling Dirty Checking: @Immutable and Read-Only Sessions

For read-only queries, dirty checking is pure overhead. Two options: `@Immutable` on the entity (Hibernate never generates UPDATE/DELETE for it — any mutation attempt is silently ignored). Or load entities in a **read-only session** — Hibernate skips snapshotting entirely, saving memory and CPU for large result sets.

Hibernate — @Immutable and read-only session hint
// @Immutable — completely prevents dirty checking for the entity
@Entity
@Immutable                // no UPDATE/DELETE will ever be generated
public class Country {
    @Id Long id;
    String code;
    String name;
}

// Read-only via Spring Data @QueryHints
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
List<Product> findAllByCategory(String category);
// Hibernate skips snapshot creation → less memory, faster flush

// Read-only via EntityManager directly
entityManager.setProperty("org.hibernate.readOnly", true);
List<Product> products = em.createQuery("SELECT p FROM Product p", Product.class)
    .setHint("org.hibernate.readOnly", true)
    .getResultList();

// Hibernate Statistics — verify dirty checking cost
Statistics stats = sessionFactory.getStatistics();
log.info("Dirty checked entities: {}", stats.getEntityUpdateCount());

Key Points to Remember

  • 1Hibernate stores a snapshot on entity load; on flush it compares and generates UPDATEs for dirty fields
  • 2No explicit save() needed for managed entities — mutation within @Transactional is enough
  • 3Flush triggers: before same-table query, on @Transactional commit, or explicit em.flush()
  • 4@DynamicUpdate generates UPDATE with only changed columns — reduces DB overhead for wide entities
  • 5@Immutable prevents any UPDATE/DELETE generation — ideal for reference/lookup tables
  • 6Read-only query hints skip snapshot creation — saves memory and flush overhead for large reads

Interview Questions

Sign in to ask Aria
1

What is dirty checking in Hibernate and when does it trigger?

EasyThoughtWorks
2

If you update a managed entity but don't call save(), will the change persist?

EasyInfosys
3

What does @DynamicUpdate do and when would you use it?

MediumOracle
4

How would you prevent dirty checking overhead for read-only queries?

MediumAtlassian
5

What is the difference between a managed entity and a detached entity with respect to dirty checking?

MediumAmazon

Ask Aria about Dirty Checking

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…