Backend Development 6 min read

Why @Transactional Can Invalidate Locks in Spring and How to Fix It

This article explains how using Spring's @Transactional annotation together with explicit locks can cause unexpected concurrency issues, demonstrates the problem with sample code, analyzes why the lock becomes ineffective, and presents solutions such as separating transactional methods, using programmatic transactions, or locking the entire transaction.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Why @Transactional Can Invalidate Locks in Spring and How to Fix It

Problem Introduction

Many developers use @Transactional for convenience, but combining it with explicit locks can lead to unexpected concurrency problems. The article illustrates this issue with a simple example where multiple threads decrement a level field concurrently.

Data Preparation

The example sets up a table where the level value is decremented by ten threads, each executing ten times.

1. No Lock

public void test() {
    // simple select + update simulation
    Model model = mapper.choseOne("99");
    // level-- operation
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}

Result: only 26 decrements are observed, indicating a concurrency issue.

2. Using a Lock

private Lock lock = new ReentrantLock();

public void test() {
    try {
        lock.lock();
        Model model = mapper.choseOne("99");
        Model updater = new Model();
        updater.setId("99");
        updater.setLevel(model.getLevel() - 1);
        mapper.updateOne(updater);
    } finally {
        lock.unlock();
    }
}

Result: the lock successfully controls the concurrency problem.

3. Using Lock + @Transactional

private Lock lock = new ReentrantLock();

@Transactional
public void test() {
    try {
        lock.lock();
        Model model = mapper.choseOne("99");
        Model updater = new Model();
        updater.setId("99");
        updater.setLevel(model.getLevel() - 1);
        mapper.updateOne(updater);
    } finally {
        lock.unlock();
    }
}

Result: only 86 decrements are observed; the lock appears to lose its effect after adding @Transactional .

Problem Analysis

@Transactional works via AOP, opening and committing a transaction before and after the target method. The lock only surrounds the method body, not the entire transaction, so the transaction may still be open when the lock is released, allowing other threads to read stale data.

Solution 1: Separate Transactional Method

private Lock lock = new ReentrantLock();

@Transactional
public void test1() {
    Model model = mapper.choseOne("99");
    Model updater = new Model();
    updater.setId("99");
    updater.setLevel(model.getLevel() - 1);
    mapper.updateOne(updater);
}

@Autowired @Lazy
private CommonService commonService;

public void test() {
    try {
        lock.lock();
        commonService.test1(); // invoke via proxy to apply transaction
    } finally {
        lock.unlock();
    }
}

Result: no concurrency issue appears.

Solution 2: Programmatic Transaction

private Lock lock = new ReentrantLock();
@Autowired
private PlatformTransactionManager transactionManager;

public void test() {
    try {
        lock.lock();
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        Model model = mapper.choseOne("99");
        Model updater = new Model();
        updater.setId("99");
        updater.setLevel(model.getLevel() - 1);
        mapper.updateOne(updater);
        transactionManager.commit(status);
    } finally {
        lock.unlock();
    }
}

Result: locking the whole transaction eliminates the problem.

In summary, when using @Transactional together with explicit locks, ensure the lock covers the entire transaction scope, either by separating transactional logic into its own method and invoking it via a Spring proxy, or by managing the transaction programmatically.

backendJavaconcurrencySpringLockTransactional
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.