Explain the suspend/resume mechanism behind NOT_SUPPORTED and its effect on data visibility and connections.
answer
- TransactionSynchronizationManager unbinds ConnectionHolder
- SuspendedResourcesHolder holds outer connection
- Inner statements = fresh auto-commit connection
- Can't see outer uncommitted writes
- Two connections held during suspend window
basics
~20 sSpring's transaction manager unbinds the active connection from the thread (suspend), runs the method with a fresh non-transactional connection, then rebinds the original (resume). Reads inside can't see the outer transaction's uncommitted changes because they use a different connection.
solid answer
~40 sWhen NOT_SUPPORTED is entered with an active transaction, AbstractPlatformTransactionManager captures the current transactional resources into a SuspendedResourcesHolder and unbinds them from the thread via TransactionSynchronizationManager. The method body then runs with nothing bound, so each statement obtains its own connection from the DataSource in auto-commit mode. Because that connection is separate from the outer transaction's connection, the method cannot see the outer transaction's uncommitted writes, and its own reads/writes are not part of the outer atomic unit. After the body returns or throws, resume() rebinds the suspended resources so the outer transaction continues. This is why NOT_SUPPORTED work survives an outer rollback and vice versa. It also requires a manager that supports suspension; managers that don't will throw TransactionSuspensionNotSupportedException.
code
java · 19 lines@Service
public class OrderService {
@Transactional // REQUIRED: opens tx, ConnectionHolder bound to thread
public void placeOrder(Order o) {
orderDao.insert(o); // uncommitted in THIS connection
long n = reportService.snapshot(); // suspends -> can't see 'o'
// ... connection rebound here, outer tx continues
}
}
@Service
class ReportService {
// Suspends the caller's tx; runs on a separate, auto-commit connection.
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public long snapshot() {
return orderDao.countCommitted(); // sees only COMMITTED rows
}
}go deeper
Know it runs on a separate connection outside the caller's transaction.
Explain that uncommitted outer changes aren't visible and work isn't atomic together.
Describe suspend/resume via TransactionSynchronizationManager and SuspendedResourcesHolder.
Weigh connection-pool doubling and visibility trade-offs before mandating NOT_SUPPORTED.
## The thread-bound resource model Spring's transaction infrastructure binds transactional resources to the current thread through `TransactionSynchronizationManager` (a set of `ThreadLocal` maps). For JDBC, a `DataSourceTransactionManager` binds a `ConnectionHolder` (wrapping the physical `Connection` with auto-commit turned off) to the `DataSource` as key. Any `DataSourceUtils.getConnection(dataSource)` call on that thread then returns the bound connection — that's how the JDBC template, JPA, and MyBatis all participate in the same transaction. ## Suspend When a `NOT_SUPPORTED` boundary is reached and a transaction is active, `AbstractPlatformTransactionManager` invokes its `suspend(...)` path. Concretely it: 1. Suspends and clears all registered `TransactionSynchronization`s. 2. Calls `doSuspend(...)`, which for `DataSourceTransactionManager` **unbinds** the `ConnectionHolder` from the thread (`TransactionSynchronizationManager.unbindResource(dataSource)`) and returns it. 3. Packages the unbound resources plus the suspended name, read-only flag, isolation level, and active flag into a `SuspendedResourcesHolder`. At this point nothing is bound to the thread — there is no active transaction. ## Running the body Inside the NOT_SUPPORTED method, when code asks for a connection, `DataSourceUtils.getConnection` finds no bound holder and fetches a **fresh connection straight from the pool**, in its default (typically auto-commit=true) mode. So: - Each statement effectively commits on its own (auto-commit) — there is no rollback scope. - This connection is a **different physical connection** from the suspended one. Under normal READ_COMMITTED isolation it therefore **cannot see the outer transaction's uncommitted changes** — you read the last committed state. ## Resume When the body finishes (normally or via exception), Spring calls `resume(...)`: `doResume(...)` **rebinds** the `ConnectionHolder` to the thread and the previously suspended synchronizations are re-registered. The outer transaction continues exactly where it left off, on its original connection. ## Visibility and atomicity consequences - **Isolation across the boundary:** the NOT_SUPPORTED reads see committed data only, not the caller's in-flight writes. This can surprise developers expecting read-your-writes. - **Independent durability:** any writes done inside NOT_SUPPORTED are committed independently (auto-commit) and are **not** rolled back if the outer transaction later rolls back; conversely an exception inside does not by itself roll back the outer transaction. - **Two connections held simultaneously:** during the suspended window, the thread has effectively two connections associated with its work — the suspended (idle-but-open) outer one and the active inner one. Under a small pool this raises the risk of pool exhaustion, especially if NOT_SUPPORTED is slow. ## Manager support requirement Suspension only works with a `PlatformTransactionManager` whose implementation supports it. `DataSourceTransactionManager`, `JpaTransactionManager`, and `JtaTransactionManager` support it. If a manager cannot suspend, Spring throws `TransactionSuspensionNotSupportedException` (a subclass of `CannotCreateTransactionException`). ## Practical implication Use NOT_SUPPORTED for a slow read where you deliberately want it *out* of the caller's transaction — but size your connection pool for the doubled connection usage during the suspended window, and be aware the read won't reflect the caller's uncommitted state.
- Why might NOT_SUPPORTED increase the chance of connection-pool exhaustion?During the suspended window the outer transaction's connection stays open and idle while the NOT_SUPPORTED body borrows a second connection from the pool. Each such thread holds two connections at once, so under a small pool and slow reads you can run out.
- Can code inside a NOT_SUPPORTED method read the caller's uncommitted changes?No, under normal READ_COMMITTED isolation. It runs on a different connection outside the caller's transaction, so it sees only committed data — not the caller's in-flight writes.
saying these in an interview costs you the question
- Claiming the NOT_SUPPORTED method reuses the outer connection
- Saying it can read the outer transaction's uncommitted changes
- Believing an inner failure rolls back the outer transaction automatically
- Assuming any transaction manager can suspend