skip to content

Transaction Integration

How the persistence context binds to a database transaction — begin, flush, commit — through resource-local or JTA coordinators. Interviewers check you know flush happens before commit and what marks a transaction rollback-only.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

questions

5

Using the JPA EntityManager API directly, walk through what happens between EntityTransaction.begin() and EntityTransaction.commit() — in particular, when does Hibernate actually send INSERT, UPDATE and DELETE statements to the database?

level: juniorimportance: must knowfreq 70%

answer

  1. begin = tx marked, auto-commit off
  2. persist queues, IDENTITY is the exception
  3. no SQL until flush
  4. commit = flush then COMMIT
  5. rollback undoes SQL, not Java fields

basics

~20 s

begin() starts one database transaction with JDBC auto-commit off. persist/merge/remove only change in-memory state. commit() first flushes, sending the queued INSERT/UPDATE/DELETE SQL, then issues COMMIT. rollback() sends ROLLBACK and the persistence context must be discarded.

solid answer

~50 s

begin() marks the start of a unit of work; Hibernate ensures the JDBC connection it uses has auto-commit disabled so everything that follows runs in one database transaction. Inside the transaction, persist(), merge() and remove() normally do not hit the database — they record intent in the persistence context. Changes to already-managed entities are not even declared: Hibernate compares each entity against the snapshot taken at load time. SQL is emitted at flush. Flush happens automatically before commit(), before a JPQL/HQL query whose tables overlap the pending changes, and when you call flush() explicitly. So commit() means: flush pending SQL, run before-completion callbacks, send COMMIT, release the connection. rollback() sends ROLLBACK. Statements already flushed are undone in the database, but the Java objects keep their modified field values, so after a rollback the EntityManager must be closed rather than reused. The one common exception to write-behind is IDENTITY id generation, which forces an immediate INSERT inside persist() to obtain the key.

code

java · 14 lines
java
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
    tx.begin();
    Order o = em.find(Order.class, 42L);
    o.setStatus(Status.PAID);        // no SQL yet
    em.persist(new AuditEntry(42L)); // no SQL yet (sequence id)
    tx.commit();                     // flush -> INSERT/UPDATE, then COMMIT
} catch (RuntimeException e) {
    if (tx.isActive()) tx.rollback();
    throw e;
} finally {
    em.close();
}

go deeper

for a junior

Be able to state the sequence: begin, work in memory, flush turns it into SQL, commit ends the database transaction, rollback discards it. Know that changing a managed entity is enough — no explicit save call.

for a middle

Explain write-behind and the three flush triggers, name IDENTITY as the exception that forces an early INSERT, and know that flushed-but-uncommitted SQL is invisible to others.

for a senior

Talk about connection acquisition timing, the cost of holding a transaction open across remote calls, and why the EntityManager must be discarded after a rollback because in-memory state no longer matches the database.

for a principal

Frame transaction boundaries as the unit of consistency and of connection-pool occupancy: boundary width drives pool sizing, lock hold time and retry design, so it is an architectural choice rather than a coding detail.

## Two layers, one boundary Two things are in play. The database transaction is a JDBC connection with auto-commit turned off, ended by COMMIT or ROLLBACK. The persistence context is Hibernate's in-memory unit of work: a map of managed entities keyed by type plus primary key, plus a queue of pending actions. EntityTransaction is the thin bridge between them: begin/commit/rollback on the JPA object translate into operations on the JDBC connection, with a flush inserted before commit. ## begin() em.getTransaction().begin() does not usually open a connection immediately. Modern Hibernate uses delayed connection acquisition: it takes a connection from the pool at the first statement that needs one. What begin() does is mark the session as being in a transaction, so that when a connection is acquired it is used in non-auto-commit mode and kept until completion. Calling begin() twice on the same active transaction throws IllegalStateException. ## Between begin and commit: write-behind Operations queue rather than execute: - persist(entity) makes the instance managed and schedules an insert. If the id generator is a sequence or table generator, Hibernate can allocate the id without touching the row, so the INSERT waits for flush. If the entity uses IDENTITY, the id only exists after the row is written, so Hibernate must execute the INSERT during persist(). - Modifying a managed entity's field schedules nothing explicitly. At flush, dirty checking compares the current field values against the loaded-state snapshot and generates UPDATE only for entities that actually changed. - remove(entity) schedules a DELETE and marks the instance removed. Write-behind exists so Hibernate can batch statements, order them to satisfy foreign keys, and skip work that turns out to be unnecessary (an entity modified and then modified back produces no UPDATE). ## Flush Flush is the act of synchronising the persistence context to the database inside the still-open transaction. It runs at three moments under the default FlushModeType.AUTO: before commit; before executing a query whose query space (the tables it reads) overlaps pending changes, so the query does not miss your own unflushed work; and on an explicit flush() call. Setting FlushModeType.COMMIT removes the query-triggered flush. Flushing is not committing. The SQL is inside the transaction and invisible to other sessions until COMMIT (subject to isolation); a later rollback undoes it. ## commit() commit() flushes, fires before-completion work, then calls Connection.commit(). After that the connection is released back to the pool and the transaction status becomes committed. If flush fails — constraint violation, optimistic-lock failure — commit() does not reach COMMIT; the provider marks the transaction rollback-only and commit() throws RollbackException. After a successful commit the entities stay managed if the EntityManager is still open (application-managed EntityManagers have an extended persistence context), so the next transaction on the same EntityManager continues to see them. ## rollback() rollback() sends ROLLBACK. What it does not do is revert Java objects: an entity whose name you changed still holds the new name, and an entity persisted with a sequence-allocated id still holds that id even though no row exists. The persistence context is now out of sync with the database, which is why the rule is to close the EntityManager after a rollback and build a fresh one to retry. ## Practical shape The canonical resource-local block is: create EntityManager, begin, do work, commit in the try, rollback in the catch when the transaction is still active, close the EntityManager in the finally. Never leave a transaction open while waiting on a remote call — the connection stays checked out and the database sees a long-idle open transaction.

  • If you never call commit and just close the EntityManager, what happens to your changes?
    Nothing is written. Closing the EntityManager with an active resource-local transaction ends the session without flushing, and the connection is returned to the pool, which rolls back any open transaction before reuse. Hibernate does not treat close() as an implicit commit, so silent data loss is the normal outcome of forgetting commit().
  • Does calling flush() make your changes visible to other transactions?
    No. flush() only sends the SQL on your own connection inside the still-open transaction. Other sessions see the rows only after COMMIT, and then only as their isolation level allows. Flushing is useful to force id generation, to surface constraint violations early, or to make a subsequent query see your own pending changes.
  • Why does Hibernate delay writes at all instead of executing each operation immediately?
    Delaying lets Hibernate batch similar statements, order inserts, updates and deletes so foreign keys and unique constraints are satisfied, collapse repeated modifications into a single UPDATE, and skip updates for entities whose fields ended up unchanged. It also shortens the window in which rows are locked by writes.

The persistence context is a shopping cart: adding items changes only the cart. Flush is putting everything on the checkout belt; commit is paying. Walking out (rollback) empties the belt but your notes about what you wanted are still in your hand.

saying these in an interview costs you the question

  • Saying persist() executes an INSERT immediately in all cases, without knowing IDENTITY is the exception
  • Believing flush() commits, or that commit() is needed after flush() only for safety
  • Thinking rollback() restores the previous field values of Java objects
  • Claiming you must call an explicit update or merge for a managed entity to be saved
  • Assuming the connection is held from begin() onward and that this is unavoidable

context

open as a page

A call to EntityManager.flush() throws a PersistenceException in the middle of a JPA transaction. What state are the transaction and the EntityManager in afterwards, and what are you allowed to do next?

level: seniorimportance: must knowfreq 52%

basics

~10 s

The provider marks the transaction rollback-only, so any later commit throws RollbackException. The persistence context is now inconsistent with the database. Roll back, discard the EntityManager, and retry the work with a fresh one.

open as a page

With a plain JPA EntityManager, do you need to call EntityTransaction.begin() before running a read-only JPQL query, and what actually happens on the JDBC connection if you do not?

level: middleimportance: should knowfreq 45%

basics

~20 s

A single query works without begin(): the connection runs it in its own implicit database transaction and it ends immediately. Multiple reads then see different committed snapshots, and any pending writes are never flushed because no commit happens.

open as a page

A JPA persistence unit can declare transaction-type RESOURCE_LOCAL or JTA. What changes for Hibernate internally and for the code that starts and ends transactions?

level: middleimportance: should knowfreq 42%

basics

~10 s

RESOURCE_LOCAL: Hibernate drives one JDBC connection itself and you call em.getTransaction().begin()/commit(). JTA: an external transaction manager owns the boundary via UserTransaction, em.getTransaction() throws, the EntityManager enlists in the ongoing transaction and flushes at before-completion.

open as a page

A service writes to a relational database through JPA and must also publish a message to a broker for the same business event. How do you decide between running the persistence unit under a JTA transaction manager with two-phase commit and keeping it resource-local with an application-level pattern?

level: principalimportance: should knowfreq 30%

basics

~20 s

Two-phase commit gives real atomicity but adds prepare round trips, a recovery log, in-doubt transactions holding locks, and XA-capable drivers. Most services instead keep one resource-local transaction, write the message as a row in the same commit, and publish it afterwards with at-least-once delivery plus idempotent consumers.

open as a page