skip to content

What do bindResource() and getResource() do, and how do they make one Connection/EntityManager shared across a transaction?

level: seniorimportance: should knowfreq 40%

answer

  1. thread-local Map keyed by factory
  2. DataSource->ConnectionHolder, EMF->EntityManagerHolder
  3. DataSourceUtils.getConnection reuses bound one
  4. bind throws if already bound
  5. ALWAYS unbind or pooled threads leak

basics

~20 s

They store and retrieve a resource (like a JDBC Connection) in a per-thread map keyed by its factory (the DataSource). Binding once lets all code in the transaction fetch the same resource instead of opening new ones.

solid answer

~50 s

TransactionSynchronizationManager holds a thread-local `Map<Object, Object>` of resources. `bindResource(key, value)` associates a resource holder with a key — the key is the factory object (the `DataSource` for JDBC, the `EntityManagerFactory` for JPA) and the value is a holder (`ConnectionHolder`, `EntityManagerHolder`). `getResource(key)` returns whatever was bound for that key on the current thread, or null. This is the mechanism that makes transactions work: when `DataSourceTransactionManager` starts a transaction, it opens one Connection and binds it. Then `DataSourceUtils.getConnection(dataSource)` — used by `JdbcTemplate`, Spring Data, etc. — calls `getResource(dataSource)` and reuses that same bound Connection instead of pulling a fresh one from the pool. That shared, thread-bound Connection is what gives you a single atomic unit of work. `bindResource` throws if a resource is already bound for the key; you must always `unbindResource` to avoid leaking into pooled threads.

code

java · 27 lines
java
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;

// Simplified illustration of what DataSourceTransactionManager does:
void begin(DataSource ds) throws SQLException {
    Connection con = ds.getConnection();
    con.setAutoCommit(false);
    TransactionSynchronizationManager.bindResource(ds, new ConnectionHolder(con));
}

// Any component that wants the SAME transactional connection:
void doWork(DataSource ds) {
    // returns the bound connection if a transaction is active, else a fresh one
    Connection con = DataSourceUtils.getConnection(ds);
    // ... use con ...
    // must release via DataSourceUtils so it is NOT closed while bound
    DataSourceUtils.releaseConnection(con, ds);
}

void end(DataSource ds) throws SQLException {
    ConnectionHolder h = (ConnectionHolder)
            TransactionSynchronizationManager.unbindResource(ds); // cleanup!
    Connection con = h.getConnection();
    con.commit();
    con.close();
}

go deeper

for a junior

Just know a Connection is stored per-thread so the whole transaction shares one connection.

for a middle

Should know the map is keyed by DataSource/EMF and that DataSourceUtils.getConnection reuses the bound connection.

for a senior

Explain the bind/get/unbind lifecycle, holders, and the pooled-thread leak gotcha; connect it to how atomicity is achieved.

for a principal

Discuss designing custom resource managers, OpenEntityManagerInView semantics, and the interplay of holders with reference counting and suspend/resume across propagation levels.

## The resource map Internally TSM keeps a `static ThreadLocal<Map<Object, Object>> resources`. The map is **keyed by the resource factory** and the value is a **resource holder** wrapping the actual resource plus reference-count/rollback metadata: | Persistence tech | Key (factory) | Value (holder) | |---|---|---| | Plain JDBC | `javax.sql.DataSource` | `ConnectionHolder` (wraps `java.sql.Connection`) | | JPA | `EntityManagerFactory` | `EntityManagerHolder` (wraps `EntityManager`) | | Hibernate native | `SessionFactory` | `SessionHolder` | ## The two calls ```java // bind at transaction start TransactionSynchronizationManager.bindResource(dataSource, new ConnectionHolder(conn)); // look up anywhere during the transaction ConnectionHolder h = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); // unbind at the end TransactionSynchronizationManager.unbindResource(dataSource); ``` - `bindResource(key, value)` — binds; **throws `IllegalStateException` if something is already bound** for that key on this thread. - `getResource(key)` — returns the bound value or `null`. - `unbindResource(key)` — removes and returns it, throwing if nothing was bound. - `unbindResourceIfPossible(key)` — quiet variant that returns null instead of throwing. - `hasResource(key)` — boolean check. ## Why it makes a transaction atomic This binding is the load-bearing trick behind Spring transactions: 1. `DataSourceTransactionManager.doBegin()` gets **one** `Connection` from the `DataSource`, sets `autoCommit=false`, wraps it in a `ConnectionHolder`, and `bindResource(dataSource, holder)`. 2. Every DB access helper — `JdbcTemplate`, `NamedParameterJdbcTemplate`, Spring Data JDBC/JPA — obtains its connection through **`DataSourceUtils.getConnection(dataSource)`**, which first calls `TransactionSynchronizationManager.getResource(dataSource)`. If a holder is bound, it reuses that exact Connection; only if none is bound does it fetch a fresh one and *not* enrol it in a transaction. 3. Because all statements run on the same Connection with autoCommit off, commit/rollback at the end is a single atomic operation over all of them. Without binding, each statement would grab its own pooled Connection and auto-commit independently — no atomicity. ## The critical gotcha: pooled threads leak Because the map is thread-local and threads are reused (Tomcat worker pool, executors), **you must unbind every resource you bind**, in a `finally`. A resource left bound will be seen by the *next* request that lands on that thread, causing it to reuse a stale/closed Connection — a nasty, intermittent production bug. Spring's own transaction managers guarantee this in their `doCleanupAfterCompletion`; if you ever bind manually you own the cleanup. ## When you'd call these yourself Almost never in business code. Legitimate cases: - Writing a custom transaction manager or a resource-manager integration. - Frameworks that must participate in Spring-managed transactions (a custom template that wants to reuse the transactional Connection via `DataSourceUtils`). - Advanced testing setups that pre-bind an `EntityManager` (`OpenEntityManagerInViewInterceptor`/`OpenSessionInView` do exactly this to keep the persistence context open for lazy loading during view rendering). ## Relationship to synchronizations Binding a resource and registering a `TransactionSynchronization` are complementary: the sync callbacks (`beforeCompletion`/`afterCompletion`) are typically where a resource holder is flushed, closed and **unbound** — which is how `OpenEntityManagerInViewInterceptor` cleans up.

  • Why must you always unbindResource in a finally block?
    The resource map is thread-local and web/executor threads are pooled and reused. A resource left bound leaks into the next task on that thread, which will reuse a stale or closed Connection/EntityManager — an intermittent, hard-to-reproduce bug.
  • How does JdbcTemplate know to reuse the transactional Connection?
    It obtains its Connection via DataSourceUtils.getConnection(dataSource), which calls TransactionSynchronizationManager.getResource(dataSource). If a ConnectionHolder is bound it reuses that exact Connection; otherwise it fetches a fresh, non-transactional one.
  • What is the key you bind against — the Connection or the DataSource?
    The factory: the DataSource (for JDBC) or EntityManagerFactory (for JPA). The value is the holder wrapping the actual Connection/EntityManager.

saying these in an interview costs you the question

  • Keying the resource map by the Connection instead of the DataSource/factory
  • Thinking you can bindResource twice for the same key (it throws)
  • Forgetting to unbind and not realizing it leaks into pooled threads
  • Believing getResource works across threads

context