What is the difference between persist, persistAndFlush, and persistFlushFind on TestEntityManager?
answer
- persist = deferred, no SQL
- persistAndFlush = INSERT runs now, gets id
- persistFlushFind = flush then reload by id
- flush != commit (still rolls back)
- reloaded instance catches mapping bugs
basics
~20 spersist makes the entity managed but sends no SQL yet (INSERT is deferred). persistAndFlush persists and then flushes so the INSERT actually runs. persistFlushFind persists, flushes, then re-reads the row by id and returns that instance, giving you a genuinely loaded entity to assert on.
solid answer
~50 sAll three make the entity managed, but they differ in how far they push it toward the database. persist queues an INSERT that JPA defers — no SQL runs until a later flush or commit. persistAndFlush adds an immediate flush(), so the INSERT executes now inside the test transaction; this is what surfaces constraint violations and generates identifiers. persistFlushFind goes further: after persist+flush it detaches the original and finds the entity back by its id, returning a freshly loaded instance. That matters because the object you passed in still lives in the first-level cache; persistFlushFind ensures you're asserting on something that came from a real load, which catches mapping mistakes (e.g. a column that never actually round-trips). Rule of thumb: persist when you'll flush later anyway, persistAndFlush to force SQL now, persistFlushFind when you want a reloaded instance.
code
java · 19 lines@DataJpaTest
class PersistVariantsTest {
@Autowired TestEntityManager em;
@Test
void variants() {
// 1) Deferred: no INSERT yet, entity is managed
em.persist(new User("[email protected]"));
// 2) Force the INSERT now; DB-generated id is assigned
User b = em.persistAndFlush(new User("[email protected]"));
assertThat(b.getId()).isNotNull();
// 3) Persist, flush, then read the row back via the load path
User loaded = em.persistFlushFind(new User("[email protected]"));
assertThat(loaded.getEmail()).isEqualTo("[email protected]");
}
}go deeper
Know persist is deferred and persistAndFlush forces the INSERT.
Explain all three, the flush-vs-commit distinction, and when each fits Arrange.
Articulate why persistFlushFind's reload catches mapping/converter/DB-default bugs the in-memory object hides.
Note caching caveats (first/second-level) mean 'find' isn't a guaranteed DB round-trip, and how batching multiple persists then one flush affects fixtures.
## The deferred-write model JPA does **not** write to the database the moment you call `persist`. The `EntityManager` batches changes and sends the SQL later, at the next **flush** or at **transaction commit** — this is called *transactional write-behind*. Understanding that timing is the whole point of these three methods. ### `persist(entity)` Makes the entity **managed** and schedules an INSERT, but **no SQL is sent yet**. The row does not exist from the database's point of view until a flush/commit. If your test never flushes and the transaction rolls back (which `@DataJpaTest` does), the INSERT may never execute at all — so any not-null/unique/foreign-key violation stays hidden. ### `persistAndFlush(entity)` Equivalent to `persist` followed immediately by `flush()`. `flush()` forces all pending SQL to the database **within the current transaction** (still not a commit — it can still be rolled back). Consequences: - The INSERT actually runs, so **DB-level constraint violations surface now** (as a `PersistenceException`/`DataIntegrityViolationException`). - Database-generated values like `IDENTITY`/sequence primary keys are assigned. It returns the same instance you passed in. ### `persistFlushFind(entity)` Does `persistAndFlush`, then **detaches the passed instance and `find`s the entity back by its id**, returning that instance. Why bother? After a plain persist/flush the object you handed in is still sitting in the **first-level cache (persistence context)**. If you assert against it, you're inspecting the in-memory object you built — not necessarily what a real load would produce. By forcing a find, `persistFlushFind` gives you an instance materialised through the normal read path, which can expose mapping bugs (a field mapped read-only, a converter that only runs on load, a default filled in by the DB). Note that with a first-level/second-level cache in play the 'find' may still be served without a fresh SELECT for the same transaction, so it is not a guarantee of a physical DB round-trip — but semantically it hands you the load-path instance. ## Choosing between them | Method | Sends SQL? | Returns | Use when | |--------|-----------|---------|----------| | `persist` | No (deferred) | the entity | You'll flush later, or you're persisting several rows and will flush once | | `persistAndFlush` | Yes (flush) | same entity | You need the INSERT to run now — to get an id or to surface a constraint violation | | `persistFlushFind` | Yes (flush) | reloaded instance | You want to assert against a genuinely loaded entity, not the object you built | ## Related helpers `persistAndGetId(entity)` persists and returns the generated id; `flush()` and `clear()` let you compose your own sequence. `clear()` after a flush is the way to force a real re-read when you keep using the same TestEntityManager for a subsequent repository call. ## Gotcha `persist` alone in a rollback-only `@DataJpaTest` is a common false-positive source: the test 'passes' because the violating INSERT never executed. Reach for `persistAndFlush`/`persistFlushFind` when the DB write must be exercised.
- Why might persistFlushFind reveal a bug that persistAndFlush hides?persistAndFlush returns the same object you built, which still lives in the persistence context. persistFlushFind reloads via find, so a field that's mapped incorrectly (e.g. insertable=false, or a DB default) shows up as different from what you set, whereas the in-memory object would still show your original value.
- Does persistAndFlush commit the transaction?No. flush() only pushes pending SQL to the database within the current transaction; @DataJpaTest still rolls that transaction back at the end. Flush makes constraints and generated ids observable without persisting data beyond the test.
saying these in an interview costs you the question
- Believing persist immediately writes a row to the database
- Thinking flush and commit are the same thing
- Assuming persistFlushFind guarantees a physical SELECT even with caching
- Using persist (without flush) and expecting constraint violations to appear