skip to content

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%

answer

  1. Single read works; each statement is its own transaction
  2. No cross-query snapshot outside a transaction
  3. Persistence context gives stable identity, not fresh data
  4. Writes queue and die silently without commit
  5. begin without commit = idle-in-transaction connection

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.

solid answer

~50 s

Reading without an explicit transaction is legal. Hibernate acquires a connection, runs the SELECT, and — since there is no transaction to keep it open — the statement commits on its own and the connection can be released. Each query is its own atomic read. The consequences are what matter. Two queries in a row are two separate database transactions, so they can see different committed states; there is no database-level repeatable read across them, even though the persistence context still returns the same Java instances for rows it already manages. Writes are worse: persist and dirty changes queue up and nothing forces a flush, so on close they disappear silently. Hibernate normally runs with auto-commit disabled on its connections. If the pool already disables it, telling Hibernate so via hibernate.connection.provider_disables_autocommit lets it delay connection acquisition. The real risk is the mirror image: opening a transaction and never committing leaves a connection checked out with an idle open transaction.

code

java · 8 lines
java
EntityManager em = emf.createEntityManager();
// no begin()
long a = em.createQuery("select count(o) from Order o where o.status = :s", Long.class)
           .setParameter("s", Status.NEW).getSingleResult();   // tx #1
List<Order> os = em.createQuery("from Order o where o.status = :s", Order.class)
           .setParameter("s", Status.NEW).getResultList();     // tx #2
// a and os.size() may differ: another session committed in between
em.close();

go deeper

for a junior

Know that a single read works without an explicit transaction, and that writes require begin plus commit or they are lost.

for a middle

Explain auto-commit semantics, why each statement outside a transaction is its own transaction, and why the persistence context can make stale data look stable.

for a senior

Discuss connection acquisition and release modes, idle-in-transaction connections from missing commits, and when read-only work genuinely needs one transaction for snapshot consistency.

for a principal

Treat transaction width as a pool-capacity and consistency decision: how many statements must agree, how long a connection may be held, and where read consistency can be relaxed deliberately.

## Auto-commit and what "no transaction" means Every statement runs inside some database transaction. With JDBC auto-commit on, the driver wraps each statement in its own transaction and commits it immediately. With auto-commit off, a transaction opens at the first statement and stays open until an explicit COMMIT or ROLLBACK. Hibernate wants auto-commit off for its unit-of-work model: write-behind depends on being able to send several statements and then commit them together. So it disables auto-commit on connections it manages, or trusts the pool to have done so when hibernate.connection.provider_disables_autocommit is set to true. ## Running a query with no EntityTransaction You can call createQuery(...).getResultList() without begin(). Hibernate obtains a connection, executes the SELECT, and, having no transaction to maintain, ends the unit immediately — the read is committed as its own transaction and the connection is released back to the pool according to the connection handling mode. This is fine for a single query. Its correctness properties are the same as any single-statement transaction: the query sees one consistent committed snapshot. ## Why it stops being fine First, multi-statement consistency disappears. Loading an order and then its line items in two queries outside a transaction means two snapshots; another session can commit in between and you can observe a half-changed world. Reports that must agree with each other need one transaction. Second, the persistence context masks part of this. If a row's entity is already managed, a later query returns the existing managed instance and discards the newly read row state. So you get identity-level stability without database-level guarantees — stable but potentially stale, and stale in a way that is hard to notice. Third, writes silently vanish. persist() and modifications to managed entities queue up. Nothing triggers a flush because no commit is coming, and closing the EntityManager throws the queue away without error. Fourth, lazy loading. Initializing a lazy association outside a transaction may still work while the session is open, but each initialization becomes another standalone read — extra round trips and yet another snapshot. ## The mirror-image problem: transactions left open Because auto-commit is off, a begin() with no commit or rollback leaves the connection holding an open transaction. On PostgreSQL that shows up as sessions idle in transaction, which blocks vacuum and can hold locks; on any engine it means the connection cannot be reused. The pool eventually rolls it back when the connection is returned, but only if the connection is returned. This is why the try/commit/catch-rollback/finally-close shape is not stylistic: it guarantees termination. ## Connection acquisition tuning Hibernate's default for resource-local units is to delay acquiring the connection until the first statement and release it after the transaction. That is why an empty transaction usually costs nothing. If Hibernate must itself disable auto-commit, it has to grab the connection at begin() to do so; declaring that the provider already disables auto-commit removes that constraint and keeps acquisition lazy. Aggressive release modes give the connection back after each statement, which is normal under JTA but interacts badly with connection-level state such as isolation or temporary objects. ## Practical guidance Wrap even read-only work in an explicit transaction when more than one statement must agree, or when you want a single connection and a single snapshot. Keep the boundary short: no remote calls, no user think time inside it. And never rely on "it worked without a transaction" for anything that writes.

  • Why does Hibernate disable JDBC auto-commit instead of leaving it on?
    Its unit-of-work model batches several statements and commits them together at flush plus commit. With auto-commit on, every statement would be its own transaction, so a failure halfway through a flush would leave partially written data with no way to undo it, and statement batching plus foreign-key-safe ordering would lose their meaning.
  • What is hibernate.connection.provider_disables_autocommit for?
    It tells Hibernate that the connection pool already hands out connections with auto-commit disabled. Hibernate can then skip touching the connection at begin() and delay acquisition until the first statement, so short transactions that end up doing no database work never check out a connection at all. Setting it when the pool does not actually disable auto-commit causes every statement to commit on its own.
  • Is a read-only transaction pointless overhead?
    No. It gives all statements in the unit one snapshot and one connection, which matters whenever several reads must be mutually consistent. The cost is a held connection for the duration, so the boundary should stay short.

saying these in an interview costs you the question

  • Claiming reads outside a transaction are impossible in JPA
  • Assuming several queries outside a transaction still see a consistent snapshot
  • Thinking close() flushes pending writes
  • Believing the persistence context returning the same values proves the data is current
  • Leaving begin() without a guaranteed commit or rollback path

context