How do you tune propagation, isolation, timeout and read-only on a TransactionTemplate for a specific unit of work, and what's the correct way to have multiple different settings?
answer
- Template IS a DefaultTransactionDefinition
- setPropagationBehavior / setIsolationLevel / setTimeout / setReadOnly / setName
- One template per policy, configure once, reuse
- Never mutate a shared template per call (race)
- Isolation only applies to the tx that starts physical tx
basics
~20 sTransactionTemplate is itself a TransactionDefinition, so you configure propagation, isolation, timeout, and read-only on the template with setters like setPropagationBehavior, setIsolationLevel, setTimeout, setReadOnly. For different settings, create separate template instances rather than mutating a shared one.
solid answer
~40 sBecause TransactionTemplate extends DefaultTransactionDefinition, its transaction settings live on the template itself: setPropagationBehavior (e.g. PROPAGATION_REQUIRES_NEW), setIsolationLevel (e.g. ISOLATION_READ_COMMITTED), setTimeout (seconds), setReadOnly(true), and setName. These are read each time execute() runs. The correct pattern for multiple policies is to create one pre-configured, immutable-after-setup template per policy — e.g. a requiresNewTemplate and a readOnlyTemplate — typically as Spring beans, and never mutate a shared template at call time. You can construct with the convenience constructor TransactionTemplate(txManager, definition) to copy an existing TransactionDefinition. Mutating a shared template's settings between calls is a thread-safety bug: concurrent callers would race on the definition. So per-call 'tuning' really means selecting the right pre-built template, not reconfiguring one on the fly.
code
java · 30 linesimport org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
public class ReportService {
private final TransactionTemplate requiresNewTx;
private final TransactionTemplate readOnlyTx;
public ReportService(PlatformTransactionManager tm) {
// Independent tx for audit that must persist even if outer rolls back
this.requiresNewTx = new TransactionTemplate(tm);
this.requiresNewTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
this.requiresNewTx.setTimeout(5); // seconds
// Read-only reporting query
this.readOnlyTx = new TransactionTemplate(tm);
this.readOnlyTx.setReadOnly(true);
this.readOnlyTx.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
this.readOnlyTx.setName("monthlyReport");
}
public void audit(String msg) {
requiresNewTx.executeWithoutResult(status -> auditRepo.write(msg));
}
public Report build() {
return readOnlyTx.execute(status -> reportRepo.aggregate());
}
}go deeper
Know the template has setters for propagation, isolation, timeout, read-only.
Explain the meaning of each setting and the sensible defaults from DefaultTransactionDefinition.
Advocate one-template-per-policy over mutating a shared template, and know isolation-on-join is ignored and read-only is a hint.
Design a small set of named, injected templates as a transaction policy layer; reason about timeout budgeting and isolation/propagation interplay under contention.
## Where the settings live `TransactionTemplate` **extends `DefaultTransactionDefinition`**, which means the template *is* a `TransactionDefinition`. The tunable attributes are therefore properties of the template object: | Setter | Meaning | Example value | |---|---|---| | `setPropagationBehavior(int)` | How this unit relates to an existing transaction | `TransactionDefinition.PROPAGATION_REQUIRES_NEW` | | `setPropagationBehaviorName(String)` | Same, by name | `"PROPAGATION_NESTED"` | | `setIsolationLevel(int)` | DB isolation | `TransactionDefinition.ISOLATION_READ_COMMITTED` | | `setIsolationLevelName(String)` | Same, by name | `"ISOLATION_SERIALIZABLE"` | | `setTimeout(int)` | Max seconds before forced rollback | `10` | | `setReadOnly(boolean)` | Read-only hint (lets the tx manager/DB optimize) | `true` | | `setName(String)` | Transaction name (monitoring/logging) | `"reportExport"` | Defaults come from `DefaultTransactionDefinition`: `PROPAGATION_REQUIRED`, `ISOLATION_DEFAULT` (use the DB's default), `TIMEOUT_DEFAULT` (-1, none), `readOnly=false`. ## What each does - **Propagation** — controls joining vs. suspending vs. requiring-new. Common values: `REQUIRED` (join or start), `REQUIRES_NEW` (suspend outer, run independent tx), `NESTED` (savepoint within outer), `SUPPORTS`, `NOT_SUPPORTED`, `MANDATORY`, `NEVER`. - **Isolation** — `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, `SERIALIZABLE`, or `DEFAULT`. Higher isolation reduces anomalies (dirty/non-repeatable/phantom reads) at the cost of concurrency. Note: isolation on `REQUIRES_NEW` starts a fresh physical tx so the level applies cleanly; setting a different isolation on a call that merely *joins* an existing transaction is not honored and, depending on the manager, can throw. - **Timeout** — measured in seconds; if the tx runs longer, it's rolled back. `-1` means the resource default (usually none). - **Read-only** — a hint. For JPA/Hibernate it can disable dirty checking and flush, and some drivers/DBs optimize accordingly. It is not a hard guarantee that writes are blocked. ## Correct pattern for multiple settings Because a template's definition is shared mutable state, you must **not** reconfigure one template per call in a concurrent app. Instead, build **one template per policy**, configure it once at startup, then reuse: ```java @Bean TransactionTemplate readOnlyTx(PlatformTransactionManager tm) { TransactionTemplate t = new TransactionTemplate(tm); t.setReadOnly(true); t.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); return t; } @Bean TransactionTemplate requiresNewTx(PlatformTransactionManager tm) { TransactionTemplate t = new TransactionTemplate(tm); t.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); t.setTimeout(5); return t; } ``` Then the call site *selects* the right template. This is what 'per-call definition tuning' means in practice — the template is configured up front, not at the moment of the call. ## Copying an existing definition The constructor `TransactionTemplate(PlatformTransactionManager tm, TransactionDefinition definition)` copies settings from any `TransactionDefinition` — handy to derive a template from an existing policy object. ## Gotchas - **Do not mutate a shared template between calls** — it's a data race (see the thread-safety question). Mutation is only acceptable during single-threaded setup. - **Isolation on a joining transaction is ignored** — only the transaction that actually starts the physical tx sets the level. Use `REQUIRES_NEW` if you truly need a different level mid-flow. - **Timeout counts total tx time**, including time your callback spends on non-DB work inside the transaction. - **read-only is a hint**, not enforcement — don't rely on it to prevent writes.
- If you set ISOLATION_SERIALIZABLE on a template call that joins an existing PROPAGATION_REQUIRED transaction, does it take effect?No. Isolation is established when the physical transaction starts. A call that merely joins an existing transaction cannot change its isolation level; depending on the transaction manager it's ignored or even throws an IllegalTransactionStateException. To get a different isolation level mid-flow you must start a new physical transaction with PROPAGATION_REQUIRES_NEW.
- What does setReadOnly(true) actually guarantee?It's a hint, not a hard guard. For Hibernate/JPA it can switch the flush mode to MANUAL and skip dirty checking, improving read performance; some drivers/databases pass it to the connection for optimization. It does not physically prevent writes — writes may still occur (and may fail or be silently unflushed depending on the provider).
saying these in an interview costs you the question
- Mutating a single shared TransactionTemplate's settings at each call site in a multithreaded app
- Assuming isolation set on a joining transaction is honored
- Treating setReadOnly(true) as a write-blocking guarantee
- Thinking timeout is in milliseconds (it's seconds)