What is manager-driven programmatic transaction management, and how do you use PlatformTransactionManager directly?
answer
- inject PlatformTransactionManager
- getTransaction / commit / rollback
- DefaultTransactionDefinition + TransactionStatus
- no proxy, boundary in code
- you must call rollback yourself
basics
~10 sInstead of the @Transactional annotation, you inject PlatformTransactionManager, call getTransaction(...) to start a transaction, run your code, then call commit() on success or rollback() on failure yourself.
solid answer
~40 sManager-driven programmatic transactions mean you control the transaction boundary in code rather than declaratively. You inject a PlatformTransactionManager bean, build a DefaultTransactionDefinition (propagation, isolation, timeout, read-only), and call txManager.getTransaction(def) which returns a TransactionStatus handle. You wrap your work in try/catch: on success call txManager.commit(status); in the catch call txManager.rollback(status) and rethrow. This is the lowest-level Spring transaction API — no AOP proxy is involved, so the boundary is explicit and visible in the method body. It is more verbose than @Transactional or TransactionTemplate but gives full control: you decide exactly when to begin, commit, or roll back, which is useful for multiple independent transactions in a loop or conditional commit logic.
code
java · 27 lines@Service
public class TransferService {
private final PlatformTransactionManager txManager;
private final AccountDao dao;
public TransferService(PlatformTransactionManager txManager, AccountDao dao) {
this.txManager = txManager;
this.dao = dao;
}
public void transfer(long from, long to, long cents) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("transfer");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
dao.debit(from, cents);
dao.credit(to, cents);
txManager.commit(status);
} catch (RuntimeException ex) {
txManager.rollback(status);
throw ex;
}
}
}go deeper
Know the three methods (getTransaction/commit/rollback) and the try/catch shape.
Should be able to configure DefaultTransactionDefinition and explain why rollback must be manual.
Contrast with @Transactional's rule-based rollback and TransactionTemplate.
Justify choosing this level only for cases the higher abstractions can't express.
## The concept Spring offers two styles of transaction management: **declarative** (the `@Transactional` annotation, driven by an AOP proxy) and **programmatic** (you write the transaction boundary in code). Programmatic itself has two flavors: the convenience wrapper `TransactionTemplate`, and the lowest level — **direct use of `PlatformTransactionManager`**. This leaf is about the latter, sometimes called *manager-driven* transactions. ## The core interface `org.springframework.transaction.PlatformTransactionManager` has three methods: - `TransactionStatus getTransaction(TransactionDefinition definition)` — begins (or joins) a transaction according to the definition and returns a handle. - `void commit(TransactionStatus status)` — commits. - `void rollback(TransactionStatus status)` — rolls back. Spring auto-configures a concrete implementation for you: `JpaTransactionManager` (JPA), `DataSourceTransactionManager` (plain JDBC/MyBatis), `JtaTransactionManager` (distributed), etc. You inject whichever bean exists. ## Defining the transaction `getTransaction` needs a `TransactionDefinition`. The usual concrete class is `org.springframework.transaction.support.DefaultTransactionDefinition`, whose setters configure: - `setPropagationBehavior(int)` — e.g. `TransactionDefinition.PROPAGATION_REQUIRED` (default), `REQUIRES_NEW`, `NESTED`. - `setIsolationLevel(int)` — e.g. `ISOLATION_READ_COMMITTED`. - `setTimeout(int seconds)`. - `setReadOnly(boolean)`. - `setName(String)` — shows up in monitoring/logs. ## The canonical pattern ```java DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("transferMoney"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // ... do work ... txManager.commit(status); } catch (RuntimeException ex) { txManager.rollback(status); throw ex; } ``` `TransactionStatus` is your handle to the running transaction. Useful methods: `isNewTransaction()`, `setRollbackOnly()`, `isRollbackOnly()`, `hasSavepoint()`, `createSavepoint()`. ## Key gotcha: you own the rollback decision Unlike `@Transactional` — which by default rolls back **only** on unchecked exceptions (`RuntimeException`/`Error`) and commits on checked exceptions — the manager does nothing automatically. If you don't call `rollback` yourself, nothing rolls back. So your `catch` block must be correct: catch broadly enough (usually `RuntimeException`, or `Throwable` if you must handle `Error`/checked too), roll back, and normally rethrow so callers see the failure. ## When to use it Reach for the direct manager only when `@Transactional` and `TransactionTemplate` don't fit: e.g. many independent transactions inside one loop (commit-per-item), transaction boundaries that must be opened in one place and closed in another, or framework/infrastructure code. For ordinary service methods, prefer `@Transactional`; for a single scoped block, prefer `TransactionTemplate` (less boilerplate, automatic rollback-on-RuntimeException).
- Which PlatformTransactionManager implementation is used with JPA?JpaTransactionManager. For plain JDBC it's DataSourceTransactionManager, and JtaTransactionManager for distributed/JTA transactions. Spring Boot auto-configures the right one based on your persistence stack.