Spring Framework provides a dependency-injection container plus focused modules
for AOP, data access, transactions, messaging, and web applications. Transaction
management demonstrates the framework’s integration model: application code
declares a boundary, while an interceptor coordinates a resource-specific
PlatformTransactionManager or reactive transaction manager.
Proxy-Based Declarative Transactions #
With the default advice mode, Spring wraps an eligible bean in an AOP proxy.
Calls entering a method matched by @Transactional pass through
TransactionInterceptor, which reads transaction metadata, starts or joins a
transaction, invokes the target, and commits or rolls back.
@Service
class OrderService {
private final OrderRepository orders;
private final PaymentGateway payments;
@Transactional
public Order place(CreateOrder command) {
Order order = orders.insert(command);
payments.reserve(order);
return order;
}
}Proxying has practical constraints. The call must enter through the proxy, and the method must be interceptable by the chosen proxy mechanism. Interface-based JDK proxies expose interface methods; class-based proxies subclass the target and cannot advise final methods.
Self-Invocation #
A call such as this.reprice() stays inside the target object and does not
re-enter its proxy. Transaction metadata on reprice() is therefore ignored
when reached only through self-invocation:
public void importOrders(List<Order> batch) {
batch.forEach(this::storeOne); // bypasses transactional advice on storeOne
}Move the transactional operation to a separate injected service when it represents a real boundary. Programmatic transaction management or AspectJ weaving are alternatives; injecting a bean’s own proxy couples domain code to container mechanics and is harder to test.
Boundary Granularity #
A transaction should contain one application-level consistency operation. Controller-level boundaries tend to include serialization and unrelated network work, while repository-level boundaries can split one business operation across multiple commits. A service method normally provides the useful unit because it coordinates repositories around a domain invariant.
Do not hold a database transaction open across an uncontrolled remote call without analyzing lock duration, retries, and failure recovery. A local transaction cannot make an external service atomic; outbox, idempotency, or saga-style coordination may be required.
Propagation, Isolation, and Rollback #
Propagation defines how a method relates to an existing transaction:
REQUIRED, the default, joins one or creates a new transaction.REQUIRES_NEWsuspends an existing transaction and starts an independent one, which may require another pooled connection.SUPPORTSjoins if one exists and otherwise runs without one.MANDATORYrequires an existing transaction;NEVERforbids one.NOT_SUPPORTEDsuspends an existing transaction.NESTEDuses a savepoint within one physical transaction when the transaction manager and resource support savepoints.
Isolation requests map to the resource’s capabilities. DEFAULT uses its
configured default; other values correspond to standard JDBC isolation levels.
A declaration is not proof that every database implements identical locking or
snapshot semantics, so verify behavior for the selected database and driver.
By default, Spring marks a transaction for rollback on RuntimeException and
Error, not on a checked exception. Configure rollbackFor or
noRollbackFor when the exception taxonomy means something different. If an
inner participant marks a shared transaction rollback-only, an outer apparent
commit can raise UnexpectedRollbackException.
Advisor Ordering #
Security, retry, cache, metrics, locking, and transaction concerns can all wrap
the same call. Nesting changes semantics: retrying inside one transaction is not
equivalent to opening a new transaction for every attempt, and caching before
authorization can expose data. Use Ordered/@Order where supported, test the
resulting call order, and avoid stacking annotations when the required order is
not explicit.
Alternatives to Declarative Proxies #
Use one of three approaches based on the required boundary:
- Refactor the operation into another service and retain declarative advice.
- Use
TransactionTemplatefor an explicit local block with visible control flow and exception handling. - Enable
@TransactionalAspectJ mode when woven self-invocation advice is an intentional system-wide choice.
Order saved = transactionTemplate.execute(status -> orders.insert(command));
Comments
Read or comment on GitHub (sign-in required to comment)