exit lab
Backend Engineering
Level 5·spring·concept·intermediate

Spring Transaction Fundamentals

Define short service-layer transaction boundaries and reason about proxy interception, propagation, rollback rules, locks, and external side effects.

55 minutes Spring Framework 6.2 / Spring Boot 3.5 imperative JDBC transactions Updated 2026-07-16
Spring Framework 6.2MyBatisPostgreSQL

Learning objectives

  • Explain how Spring applies declarative transactions through a proxy
  • Design transaction boundaries that protect data without including remote calls

What it is

A database transaction groups reads and writes into one commit or rollback boundary with an isolation policy. Spring declarative transaction management uses metadata such as @Transactional and an AOP proxy around an externally invoked method to drive a transaction manager.

Why it matters

Business operations such as order creation, balance movement, or voucher redemption must not expose partial durable state. At the same time, long transactions hold connections and locks, increasing contention and failure impact.

How it works

Declarative transaction call

Controller
Spring service proxy
Begin / join transaction
Service method
MyBatis operations
Commit or rollback
Text alternative: The controller calls the Spring-managed service proxy. The proxy begins or joins a transaction, invokes the service method and its MyBatis operations, then commits on success or rolls back when a configured rollback rule matches.
DefaultSpring behavior
PropagationREQUIRED: join an existing transaction or create one
IsolationUse the underlying data source default
Read/writeRead-write
RollbackRuntimeException and Error roll back; checked Exception does not by default
Proxy modeOnly calls that enter through the proxy are intercepted

Simple example

OrderService.java
@Service
public class OrderService {
    private final OrderMapper orderMapper;
    private final OutboxMapper outboxMapper;

    public OrderService(OrderMapper orderMapper, OutboxMapper outboxMapper) {
        this.orderMapper = orderMapper;
        this.outboxMapper = outboxMapper;
    }

    @Transactional
    public long create(CreateOrderCommand command) {
        OrderRow order = OrderRow.create(command);
        orderMapper.insert(order);
        outboxMapper.insert(OrderCreatedEvent.from(order));
        return order.id();
    }
}

Order and outbox rows commit together. A separate relay publishes the outbox event later, so a slow or unavailable broker does not keep the database transaction open.

Backend example

VoucherService.java
@Transactional
public RedemptionResponse redeem(long voucherId, String idempotencyKey, long actorId) {
    RedemptionRow existing = redemptionMapper.findByKey(actorId, idempotencyKey);
    if (existing != null) return RedemptionResponse.from(existing);

    int changed = voucherMapper.redeemIfIssued(voucherId, actorId);
    if (changed == 0) throw new VoucherNotRedeemableException(voucherId);

    RedemptionRow created = RedemptionRow.create(voucherId, actorId, idempotencyKey);
    redemptionMapper.insert(created);
    return RedemptionResponse.from(created);
}

Production example

  1. 1

    Before

    Validate request syntax and obtain required non-database context before opening the transaction.

  2. 2

    Inside

    Read and write only the data required for one invariant; use stable lock order and explicit affected-row checks.

  3. 3

    After commit

    Perform remote calls or publish via an outbox/after-commit mechanism with its own retry and idempotency policy.

  4. 4

    Failure

    Classify conflicts, deadlocks, timeouts, and infrastructure failures; retry only safe operations with a bounded policy.

Common mistakes

  • Annotating a private method and expecting proxy interception
  • Calling a transactional method from another method on the same object
  • Expecting checked exceptions to roll back by default
  • Performing email, payment, or HTTP calls while holding database locks
  • Using REQUIRES_NEW without analyzing independent commit and pool demand
  • Catching an exception and returning success after the transaction is marked rollback-only

Best practices

  • Put use-case transactions on concrete service methods
  • Keep boundaries short and observable
  • State rollback rules explicitly for checked business exceptions
  • Use database constraints and atomic SQL inside the boundary
  • Publish integration events with an outbox where consistency requires it
  • Test actual rollback and concurrency, not only annotations

Trade-offs

ChoiceUseCost
REQUIREDOne atomic use caseInner work shares rollback fate
REQUIRES_NEWTruly independent durable workExtra connection; outer rollback cannot undo it
Optimistic controlConflicts are rareCaller must handle retry/conflict
Pessimistic lockShort high-value serializationBlocking and deadlock risk

Interview questions

  • How does @Transactional work?
  • Why does self-invocation matter?
  • Which exceptions roll back by default?
  • Why avoid external HTTP calls inside a transaction?
  • How would you prevent a lost balance update?
  • What can cause UnexpectedRollbackException?

Hands-on task

  1. 1

    Rollback

    Prove a two-write service rolls back on a runtime failure and document checked-exception behavior.

  2. 2

    Proxy

    Write a self-invocation example, observe the missing boundary, and refactor it.

  3. 3

    Concurrency

    Run simultaneous voucher redemption requests and verify one durable result with a stable response policy.

References

  • Spring: Using @Transactional — https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html
  • PostgreSQL Global Development Group: Transaction Isolation — https://www.postgresql.org/docs/18/transaction-iso.html
  • MyBatis: MyBatis Spring Boot Starter Reference — https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
  • Microservices.io: Pattern: Transactional Outbox — https://microservices.io/patterns/data/transactional-outbox.html

Retrieval check

Before you continue

  • Explain the main idea in your own words without rereading the page.
  • Name one production problem this knowledge helps prevent.
  • Describe where you would apply it in a Spring Boot project.

Source metadata

Primary references checked on 2026-07-16. Version scope is stated at the top of this page.