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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.