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?
answer
- RESOURCE_LOCAL = Hibernate drives Connection.commit()
- JTA = UserTransaction drives it, getTransaction() throws
- jta-data-source enlists, non-jta-data-source does not
- joinTransaction for EM created outside the tx
- flush happens at beforeCompletion under JTA
basics
~10 sRESOURCE_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.
solid answer
~50 sWith RESOURCE_LOCAL the persistence unit owns exactly one resource — a JDBC DataSource declared as non-jta-data-source. Hibernate installs its JDBC transaction coordinator, so begin/commit/rollback on EntityTransaction map straight to auto-commit off, Connection.commit() and Connection.rollback(). One database, no external coordinator. With JTA the boundary belongs to a transaction manager. You start work with UserTransaction or a container-managed boundary; calling em.getTransaction() throws IllegalStateException. Hibernate installs its JTA transaction coordinator, obtains connections from the jta-data-source (which enlists them with the transaction manager), and registers a synchronization: at before-completion it flushes the persistence context, at after-completion it reacts to the outcome and, for transaction-scoped EntityManagers, closes the persistence context. Practical consequences: multiple resources can commit atomically through two-phase commit; an EntityManager created outside a transaction may need joinTransaction(); and rollback-only is set through the transaction manager rather than EntityTransaction.
code
java · 11 lines// RESOURCE_LOCAL
em.getTransaction().begin();
em.persist(order);
em.getTransaction().commit();
// JTA
userTransaction.begin();
EntityManager em = emf.createEntityManager();
em.joinTransaction(); // required: EM created after tx started
em.persist(order);
userTransaction.commit(); // Hibernate flushes at beforeCompletiongo deeper
Know that resource-local means you call begin/commit yourself on one database, and that JTA means something else owns the boundary.
Explain the coordinator swap, that getTransaction() throws under JTA, the role of jta-data-source enlistment, and joinTransaction().
Discuss flush at before-completion, transaction-scoped persistence-context closure at after-completion, and diagnosing lost writes caused by an unjoined EntityManager.
Judge whether multi-resource atomicity is worth XA's latency, recovery-log operations and driver constraints, versus keeping a single resource and solving cross-system consistency at the application level.
## What the setting actually selects transaction-type in persistence.xml decides who owns transaction demarcation, and therefore which Hibernate transaction coordinator is used. RESOURCE_LOCAL means the persistence unit manages its own single resource. Hibernate uses its JDBC resource-local transaction coordinator: it turns off auto-commit and calls commit or rollback on the JDBC Connection directly. The persistence unit should point at non-jta-data-source (or plain JDBC connection settings). JTA means an external Java Transaction API implementation — an application server's transaction manager or a standalone one — owns the transaction. Hibernate uses its JTA coordinator, looks up the TransactionManager and TransactionSynchronizationRegistry through its platform strategy, and takes connections from jta-data-source, a datasource that enlists each connection as a resource in the active global transaction. ## The programming model differs Under RESOURCE_LOCAL the code is explicit: em.getTransaction().begin(), work, commit() or rollback(), with isActive() to test status. Under JTA, EntityTransaction is unavailable — getTransaction() throws IllegalStateException — and you demarcate with UserTransaction.begin()/commit(), or let a container do it. Marking failure means UserTransaction.setRollbackOnly() rather than EntityTransaction.setRollbackOnly(). ## Enlistment and joinTransaction A JTA EntityManager must be associated with the current global transaction to have its flush tied to commit. A transaction-scoped EntityManager created while a transaction is active joins automatically. One created outside a transaction, or an application-managed one, must call joinTransaction() after the transaction starts; forgetting it is a classic bug where writes appear to vanish because the persistence context never flushes as part of the commit. ## Flush and lifecycle callbacks The JTA coordinator registers a Synchronization. beforeCompletion() is where Hibernate flushes — the SQL must be on the connection before the transaction manager begins its prepare phase. afterCompletion(status) tells Hibernate whether to treat the work as committed or rolled back; for a transaction-scoped persistence context it also closes it, detaching every managed entity at that instant. Under RESOURCE_LOCAL the same ordering exists but Hibernate controls it directly inside commit(). ## Multi-resource atomicity The reason to choose JTA is more than one transactional resource in one atomic unit: two databases, or a database plus a message broker. The transaction manager runs two-phase commit — prepare each resource, then commit each — and keeps a recovery log so in-doubt branches can be resolved after a crash. That requires XA-capable drivers and datasources. RESOURCE_LOCAL cannot do this: two persistence units under RESOURCE_LOCAL are two independent transactions that can succeed and fail independently. ## Costs and gotchas JTA adds latency (extra prepare round trips), operational burden (recovery logs, in-doubt transactions holding locks), and constraints on connection handling — aggressive connection release is the safe default under JTA because the transaction manager can suspend and resume. Isolation settings are typically configured on the datasource rather than by Hibernate. And Hibernate must be told which platform it is on so it can find the transaction manager; a misconfigured platform strategy shows up as flushes that never happen at the right time or as no-transaction-active errors. A single-database service almost always wants RESOURCE_LOCAL; introducing JTA for one resource buys nothing but complexity.
- What happens if you call em.getTransaction() in a JTA persistence unit?It throws IllegalStateException. The specification forbids resource-local demarcation when the persistence unit is JTA, because the boundary is owned by the transaction manager and a second, independent commit path would break atomicity across enlisted resources.
- You persist entities inside a JTA transaction and nothing is written, with no exception. What do you check first?Whether the EntityManager is actually joined to the transaction. An application-managed EntityManager created before or outside the transaction must call joinTransaction(); otherwise no synchronization is registered, no flush runs at before-completion, and the persistence context is simply discarded at close. Also confirm the datasource is the JTA one, so its connection is enlisted.
saying these in an interview costs you the question
- Believing JTA is required whenever there is more than one table or more than one entity
- Claiming em.getTransaction().commit() also commits the JTA transaction
- Assuming a transaction-scoped EntityManager always joins, even when created before the transaction started
- Using a non-JTA datasource inside a JTA persistence unit and expecting the writes to be part of the global transaction
- Treating two-phase commit as free, ignoring prepare latency and in-doubt recovery