Spring Overview and Transactions

Last update: 2025-05-25 03:36:19 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

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.

java
@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:

java
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_NEW suspends an existing transaction and starts an independent one, which may require another pooled connection.
  • SUPPORTS joins if one exists and otherwise runs without one.
  • MANDATORY requires an existing transaction; NEVER forbids one.
  • NOT_SUPPORTED suspends an existing transaction.
  • NESTED uses 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:

  1. Refactor the operation into another service and retain declarative advice.
  2. Use TransactionTemplate for an explicit local block with visible control flow and exception handling.
  3. Enable @Transactional AspectJ mode when woven self-invocation advice is an intentional system-wide choice.
java
Order saved = transactionTemplate.execute(status -> orders.insert(command));

Primary References #

Comments

Read or comment on GitHub (sign-in required to comment)