Can JdbcTemplate participate in the same transaction as JPA under JpaTransactionManager? How does the connection get shared?
answer
- set JpaTransactionManager.dataSource (Boot does it)
- ConnectionHolder bound by DataSource key
- DataSourceUtils.getConnection returns the bound connection
- MUST be the same DataSource bean
- em.flush() before raw JDBC read; no XA needed
basics
~20 sYes. JpaTransactionManager exposes the JDBC Connection that Hibernate uses and binds it to the DataSource in Spring's resource registry. A JdbcTemplate built on that same DataSource picks up the bound connection, so JDBC and JPA share one transaction and commit together.
solid answer
~40 sYes, provided JdbcTemplate uses the exact same DataSource that the JPA EntityManagerFactory is built on. JpaTransactionManager, when its dataSource property is set (Spring Boot sets it automatically), extracts the JDBC Connection from Hibernate's EntityManager and binds it as a ConnectionHolder in TransactionSynchronizationManager keyed by that DataSource. JdbcTemplate obtains its connection via DataSourceUtils.getConnection, which returns the bound one instead of opening a new physical connection. So both run on one connection and one transaction; they commit or roll back atomically — no XA/JTA needed. The critical gotcha: JDBC writes and JPA writes see each other only correctly if you flush the persistence context (em.flush()) before the raw JDBC query, because Hibernate's pending changes aren't in the DB until flushed. Also both must share the same DataSource bean, or you silently get two independent transactions.
code
java · 24 lines@Configuration
class PersistenceConfig {
@Bean JpaTransactionManager transactionManager(EntityManagerFactory emf, DataSource ds) {
JpaTransactionManager tm = new JpaTransactionManager(emf);
tm.setDataSource(ds); // enables ConnectionHolder binding for JdbcTemplate sharing
return tm;
}
@Bean JdbcTemplate jdbcTemplate(DataSource ds) { return new JdbcTemplate(ds); } // SAME ds bean
}
@Service
class ReportService {
@PersistenceContext EntityManager em;
private final JdbcTemplate jdbc;
ReportService(JdbcTemplate jdbc) { this.jdbc = jdbc; }
@Transactional
public int archiveAndCount(Order o) {
em.persist(o);
em.flush(); // push JPA insert to the DB so the JDBC query below can see it
// runs on the SAME connection/transaction as the JPA insert:
return jdbc.queryForObject("select count(*) from orders", Integer.class);
}
}go deeper
Know JdbcTemplate and JPA can share one transaction if on the same DataSource.
Explain ConnectionHolder binding by DataSource and DataSourceUtils returning it to JdbcTemplate.
Handle the flush-ordering visibility gotcha and the same-DataSource requirement.
Contrast single-connection local atomicity vs. XA/JTA for multi-resource, and stale-cache mitigation after JDBC writes.
**The goal.** Sometimes you mix ORM and raw SQL: JPA for entities, `JdbcTemplate` for a bulk update or a reporting query. You want both to run inside ONE database transaction so they commit or roll back together, without resorting to distributed (XA/JTA) transactions. **How JpaTransactionManager enables it.** `JpaTransactionManager` has an optional `dataSource` property. In Spring Boot it's set to the same `DataSource` the `EntityManagerFactory` uses. When set, at transaction begin the manager not only binds the `EntityManagerHolder` (keyed by the `EntityManagerFactory`) but also obtains the JDBC `Connection` that Hibernate opened for this transaction and binds it as a `ConnectionHolder` in `TransactionSynchronizationManager`, keyed by the **DataSource**. (Technically it uses the JPA dialect / `HibernateJpaDialect` to expose the underlying connection.) **How JdbcTemplate joins in.** `JdbcTemplate` never opens connections directly; it calls `DataSourceUtils.getConnection(dataSource)`. That utility first checks `TransactionSynchronizationManager` for a `ConnectionHolder` bound to that `DataSource`. If one is present (bound by `JpaTransactionManager`), it returns that **same physical connection** and marks it as not-to-be-closed by the template. Result: the JDBC statements execute on Hibernate's connection, inside Hibernate's transaction. On commit, the single connection is committed once. **Hard requirements.** - **Same DataSource bean.** `JdbcTemplate` must be constructed with the identical `DataSource` instance that the `EntityManagerFactory` was built on and that is set as `JpaTransactionManager.dataSource`. Different `DataSource` instances (even to the same DB) = different resource keys = two independent transactions, no atomicity. - **dataSource set on the manager.** If `JpaTransactionManager.dataSource` is not set, it won't bind the `ConnectionHolder`, so `JdbcTemplate` opens its own connection/transaction. Boot sets it; manual configs sometimes forget. **Visibility gotcha — flush ordering.** Because JPA is write-behind, entity changes you made in memory are NOT in the database until Hibernate flushes. A subsequent `JdbcTemplate` query reads the DB directly and will NOT see unflushed JPA changes. Conversely, a `JdbcTemplate` write bypasses Hibernate, so the persistence context won't know about it (stale first-level cache). Rules of thumb: - Call `em.flush()` before a `JdbcTemplate` read that must see JPA changes. - After a `JdbcTemplate` write to rows JPA also manages, consider `em.clear()`/refresh to avoid stale cached entities. **Rollback semantics.** Since it's one connection/one transaction, if either side fails and the transaction rolls back, both the JPA and JDBC work are undone together. No two-phase commit is involved — this is a single local resource. **When you'd still need JTA.** If the JDBC work targets a *different* database/resource, you can't share one connection; you'd need `JtaTransactionManager` with XA for atomicity across resources.
- What happens if the JdbcTemplate uses a different DataSource than the EntityManagerFactory?They run in two separate transactions on two connections. No shared transaction or atomicity; a rollback on one won't undo the other. You'd need the same DataSource, or JTA/XA for true multi-resource atomicity.
- Why might a JdbcTemplate SELECT not see an entity you just persisted?JPA defers SQL until flush. Until you call em.flush(), the INSERT hasn't reached the database, so a raw JDBC read on the same connection sees nothing. Flush first.
- Does sharing the connection require XA/two-phase commit?No. It's a single physical connection and a single local transaction, so a plain resource-local commit is atomic across both JPA and JDBC. XA is only for spanning multiple resources.
saying these in an interview costs you the question
- Assuming JPA and JdbcTemplate share a transaction even with different DataSource beans
- Forgetting em.flush() before a raw JDBC read and concluding the data 'disappeared'
- Thinking you need JTA/XA to combine JPA and JdbcTemplate on one database
- Not setting JpaTransactionManager.dataSource in a manual config and expecting sharing