skip to content

What does saveAndFlush do that save does not, and when would you use flush()?

level: middleimportance: must knowfreq 70%

answer

  1. flush = send SQL now, NOT commit
  2. save stages, flush executes
  3. use for generated id / early constraint failure / read-after-write
  4. loop of saveAndFlush kills batching
  5. FlushModeType AUTO vs COMMIT

basics

~10 s

save() stages the change in the persistence context; the SQL may run later. saveAndFlush() saves and immediately runs flush(), forcing the INSERT/UPDATE to the database now — still inside the transaction, not a commit.

solid answer

~40 s

With JPA, save() registers the entity in the persistence context but Hibernate defers the actual SQL until flush time (usually before a query or at commit). saveAndFlush() calls save() then flush(), so the INSERT/UPDATE is sent to the database immediately. Crucially, flush ≠ commit: the SQL executes within the current transaction and can still be rolled back. You reach for saveAndFlush/flush when order-of-execution matters within one transaction: you need a database-generated value or want a DB constraint (unique, not-null) to fail *now* so you can handle it; you're about to run a native or JPQL query that must see the written rows; or you're mixing JPA writes with JDBC. Overusing it defeats Hibernate's write-batching and can hurt performance, so use it deliberately, not by default.

code

java · 11 lines
java
@Transactional
public void register(User user) {
    try {
        userRepository.saveAndFlush(user); // force the INSERT now
    } catch (DataIntegrityViolationException e) {
        // unique(email) violation surfaces HERE, not at commit
        throw new EmailAlreadyUsedException(user.getEmail(), e);
    }
    // subsequent native query in this tx now sees the row
    auditRepository.recordNativeInsert(user.getId());
}

go deeper

for a junior

Know save stages the change and saveAndFlush pushes it to the DB immediately.

for a middle

Explain flush ≠ commit and give concrete reasons to flush (generated id, early constraint failure, read-after-write).

for a senior

Discuss FlushModeType, batching trade-offs, and exception translation at flush time.

for a principal

Reason about transaction/flush boundaries across service layers and the performance impact of premature flushing at scale.

**Background: the persistence context and dirty checking.** When you work with JPA/Hibernate inside a transaction, entities live in a *persistence context* (the first-level cache / EntityManager). Changes you make — `save()`, or just mutating a managed entity — are recorded but the SQL is not necessarily sent to the database immediately. Hibernate *flushes* (sends the pending SQL) at well-defined points: before executing a query that might be affected, and at transaction commit. This deferral lets Hibernate batch and reorder statements for efficiency. **save().** `SimpleJpaRepository.save(entity)` either calls `EntityManager.persist(entity)` (for a new/transient entity, identified by a null/absent id or `EntityInformation.isNew`) or `EntityManager.merge(entity)` (for a detached/existing one). Either way the write is staged in the persistence context; the actual INSERT/UPDATE may not hit the DB until a later flush. **flush().** `EntityManager.flush()` forces Hibernate to synchronise the persistence context with the database *right now* — all pending INSERT/UPDATE/DELETE statements are executed. **This is not a commit.** The statements run inside the still-open transaction; if the transaction later rolls back, they are undone. `JpaRepository.flush()` exposes this at the repository level. **saveAndFlush().** Simply `save(entity)` followed by `flush()`. The row is written to the DB immediately (within the transaction). **When to use it:** 1. **You need a DB-generated value now** — e.g., an `@GeneratedValue(strategy = IDENTITY)` id is only assigned once the INSERT runs; with IDENTITY, persist actually triggers the insert eagerly, but for other strategies or trigger-populated columns, flushing forces population. 2. **You want a constraint violation to surface early** — flushing makes a unique/not-null/FK violation throw a `PersistenceException`/`DataIntegrityViolationException` at the flush point, so you can catch and handle it precisely instead of at commit where it's harder to attribute. 3. **Read-after-write within the same transaction via a bypassing query** — a subsequent JPQL/native query reads from the DB, not the persistence context. If you wrote via save() and the change hasn't flushed, a native query won't see it. Flushing first guarantees visibility. (Hibernate auto-flushes before JPQL by default, but native SQL and certain flush modes may not.) 4. **Mixing JPA and plain JDBC** in one transaction where the JDBC path must see JPA writes. **Gotchas and trade-offs:** - **flush ≠ commit** is the number-one misconception. Data is not durable until the transaction commits. - **Performance:** default deferred flushing lets Hibernate batch inserts/updates. Calling saveAndFlush in a loop forces a round-trip per iteration and disables batching — use `saveAll` then a single `flush`, or `saveAllAndFlush`, instead. - **FlushModeType:** the EntityManager's flush mode (AUTO vs COMMIT) controls automatic flushing. AUTO (default) flushes before queries; COMMIT only at commit. Explicit flush() overrides regardless of mode. - **Exceptions:** a flush can throw `OptimisticLockException` (version mismatch) or constraint violations; these translate to Spring's `DataAccessException` hierarchy via the repository proxy's exception translation. **Rule of thumb:** default to save(); use saveAndFlush/flush only when execution order within the transaction demands it.

  • Does saveAndFlush commit the transaction?
    No. It executes the SQL against the database but inside the current transaction; a later rollback undoes it. Only the transaction commit makes the change durable.
  • Why is calling saveAndFlush inside a loop a bad idea?
    It forces a database round-trip on every iteration and disables Hibernate's JDBC batching. Prefer saveAll(list) followed by a single flush(), or saveAllAndFlush(list).

saying these in an interview costs you the question

  • Saying flush is the same as commit / makes data durable
  • Using saveAndFlush everywhere 'to be safe'
  • Believing save() always executes the INSERT immediately for all id strategies

context