Backend Development 9 min read

Avoiding Long Transactions in Spring: Lessons from a Production Incident

This article explains how using Spring's @Transactional annotation can unintentionally create long-running transactions, illustrated by a production incident where database connections were exhausted, and provides strategies such as method splitting, programmatic transactions, and proxy usage to avoid such pitfalls.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Avoiding Long Transactions in Spring: Lessons from a Production Incident

Spring makes transaction management easy by adding the @Transactional annotation to a method, which automatically starts, commits, or rolls back a transaction.

The author recounts a production incident in 2019 where an internal reimbursement system used @Transactional on a method that called an external workflow service, leading to a long transaction that exhausted the database connection pool, caused deadlocks, and resulted in a system outage.

Analysis shows that the problem was the long‑running transaction: the method held the same database connection while waiting for a remote RPC call, filling the connection pool.

Long transactions (or “big transactions”) cause connection pool exhaustion, deadlocks, long rollback times, and increased replication lag.

To avoid long transactions, the transaction scope should be reduced by splitting business logic, using programmatic transactions, or moving non‑transactional work to separate methods or classes.

Declarative transactions ( @Transactional ) are simple but have the drawback that the transaction boundary is the whole method, making fine‑grained control difficult; programmatic transactions via TransactionTemplate allow precise control.

Example of a declarative transaction that caused the incident:

/**
 * 保存报销单并创建工作流
 */
@Transactional(rollbackFor = Exception.class)
public void save(RequestBillDTO requestBillDTO){
    //调用流程HTTP接口创建工作流
    workflowUtil.createFlow("BILL",requestBillDTO);
    //转换DTO对象
    RequestBill requestBill = JkMappingUtils.convert(requestBillDTO, RequestBill.class);
    requestBillDao.save(requestBill);
    //保存明细表
    requestDetailDao.save(requestBill.getDetail())
}

Example of using programmatic transaction with TransactionTemplate :

@Autowired
private TransactionTemplate transactionTemplate;

public void save(RequestBill requestBill){
    transactionTemplate.execute(transactionStatus -> {
        requestBillDao.save(requestBill);
        //保存明细表
        requestDetailDao.save(requestBill.getDetail());
        return Boolean.TRUE;
    });
}

Refactoring by extracting the transactional part into a separate method:

@Service
public class OrderService {
    public void createOrder(OrderCreateDTO createDTO){
        query();
        validate();
        saveData(createDTO);
    }

    @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.insert(createDTO);
    }
}

When the transactional method is called from within the same class, Spring AOP proxy is bypassed and the transaction does not take effect. Solutions include moving the transactional method to another bean (e.g., a manager class) or exposing the proxy:

@Service
public class OrderService {
    @Autowired
    private OrderManager orderManager;

    public void createOrder(OrderCreateDTO createDTO){
        query();
        validate();
        orderManager.saveData(createDTO);
    }
}

@Service
public class OrderManager {
    @Autowired
    private OrderDao orderDao;

    @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.saveData(createDTO);
    }
}

Alternatively, enable proxy exposure and invoke the transactional method via the proxy:

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {}

public void createOrder(OrderCreateDTO createDTO){
    OrderService orderService = (OrderService)AopContext.currentProxy();
    orderService.saveData(createDTO);
}

The article concludes that while @Transactional is convenient, developers must be aware of its pitfalls; for complex business logic, programmatic transactions or proper method splitting are recommended to prevent long‑transaction issues.

backendtransactionSpringLong TransactionProgrammatic Transaction
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.