How to Tame Large Transactions in Spring: 6 Proven Strategies

This article shares practical techniques for handling large transactions in Spring-based backend systems, covering why @Transactional can cause issues, how to use programmatic transactions, move queries outside transactions, avoid remote calls, limit data volume, execute non‑transactional operations, and leverage asynchronous processing.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
How to Tame Large Transactions in Spring: 6 Proven Strategies

Preface

A reader asked how to handle large‑transaction problems in a system. The author recounts a recent project where tight schedules led to ignoring performance issues, later allocating an iteration to resolve large‑transaction problems and summarizing the solutions.

Problems Caused by Large Transactions

Large transactions can cause lock contention, long response times, and high failure risk, as illustrated in the diagram below.

Large transaction issues diagram
Large transaction issues diagram

Solutions

Reduce Use of @Transactional Annotation

Declarative transactions are enabled by the @Transactional annotation, which relies on Spring AOP. Overusing it makes the transaction scope too coarse and can cause failures.

Example code:

@Transactional(rollbackFor=Exception.class)
public void save(User user) {
    // business logic
}

Reasons to limit its use:

Declarative transaction works through Spring AOP; misuse may render the transaction ineffective.

Applying @Transactional to an entire business method creates a transaction that is too large.

Use Programmatic Transactions

Programmatic transactions give fine‑grained control via TransactionTemplate.

@Autowired
private TransactionTemplate transactionTemplate;

public void save(final User user) {
    transactionTemplate.execute(status -> {
        // business logic
        return Boolean.TRUE;
    });
}

Move Select Queries Outside Transactions

Read‑only queries usually do not need a transaction and should be executed before the transactional block.

@Transactional(rollbackFor=Exception.class)
public void save(User user) {
    queryData1();
    queryData2();
    addData1();
    updateData2();
}

Refactored version:

public void save(User user) {
    queryData1();
    queryData2();
    transactionTemplate.execute(status -> {
        addData1();
        updateData2();
        return Boolean.TRUE;
    });
}

Split @Transactional Method

Separate the transactional part into another service method or obtain the proxy via AopContext to ensure the transaction is applied.

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void save(User user) {
        queryData1();
        queryData2();
        serviceB.doSave(user);
    }
}

@Service
public class ServiceB {
    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
        addData1();
        updateData2();
    }
}

Avoid Remote Calls Inside Transactions

Remote API calls, MQ messages, or external store accesses should be performed outside the transaction to prevent long‑running large transactions.

public void save(User user) {
    callRemoteApi(); // outside transaction
    transactionTemplate.execute(status -> {
        addData1();
        return Boolean.TRUE;
    });
}

Compensating actions and retries can guarantee eventual consistency.

Limit Data Processed Per Transaction

Process large data sets in pages (e.g., split 1000 rows into 50 pages of 20 rows) to reduce lock contention and avoid oversized transactions.

Execute Non‑Transactional Operations Separately

Operations such as logging or statistics updates can be performed after the transaction, as slight inconsistency is acceptable.

public void save(User user) {
    transactionTemplate.execute(status -> {
        addData();
        return Boolean.TRUE;
    });
    addLog();
    updateCount();
}

Asynchronous Processing

Long‑running tasks (e.g., order delivery) should be sent to a message queue and processed asynchronously, keeping the transaction short.

public void save(User user) {
    transactionTemplate.execute(status -> {
        order();
        return Boolean.TRUE;
    });
    sendMq();
}

Summary

The author proposes six practical ways to handle large transactions: reduce @Transactional usage, move select queries out of transactions, avoid remote calls inside transactions, limit batch size, execute non‑transactional steps separately, and employ asynchronous processing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

springtransaction-managementLarge TransactionsProgrammatic TransactionDeclarative Transaction
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

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.