Does @Transactional Commit Before Unlock? Deep Dive into Spring Transaction Timing

This article explores the precise moment a Spring @Transactional transaction is committed relative to a surrounding lock release, revealing that commits occur after the method finishes, and demonstrates how to verify this through source code inspection, debugging techniques, and proper lock‑usage to prevent overselling.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Does @Transactional Commit Before Unlock? Deep Dive into Spring Transaction Timing

Problem Overview

A colleague posted a code snippet that first queries product inventory, then, if stock exists, decrements it and inserts an order. Both operations modify the database and should be atomic, so the method is annotated with @Transactional and wrapped in a lock to ensure only one thread modifies the stock at a time.

Why Transaction Timing Matters

MySQL uses the REPEATABLE READ isolation level by default. In a high‑concurrency scenario, if the transaction is committed after the lock is released, another thread may read stale stock data and also place an order, leading to overselling.

Two cases are considered:

If the transaction commits before unlock, the lock still protects the critical section and overselling cannot happen.

If the transaction commits after unlock, the lock no longer protects the data, and overselling is possible.

Source Code Analysis

The lock is applied around the business logic, and the @Transactional annotation triggers Spring’s transaction management. The actual transaction start occurs in

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

, which switches the JDBC connection to manual commit (sets autoCommit to false).

Spring’s transaction interceptor (

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

) creates the transaction before the business method runs and commits it after the method returns, unless an exception marks the transaction as rollback‑only.

Key code snippets:

@Transactional
con.setAutoCommit(false)
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

When an exception occurs, Spring decides whether to roll back based on RuleBasedTransactionAttribute#rollbackOn. By default, RuntimeException and Error trigger a rollback.

Debugging Tips

To see where the transaction actually begins, set a breakpoint in DataSourceTransactionManager#doBegin and observe the log line:

Switching JDBC Connection [...] to manual commit

To verify the commit point, place a breakpoint in AbstractPlatformTransactionManager#commit. The commit will only happen if the transaction is not marked as rollback‑only.

Additional debugging tricks include:

Breakpoint on java.sql.Connection#setAutoCommit to see which implementation is used.

Breakpoint on TransactionAspectSupport#invokeWithinTransaction to trace the call stack.

Breakpoint on com.mysql.cj.protocol.a.NativeProtocol#sendQueryString to stop only on actual SQL commit statements.

Solution

The correct approach is to ensure the entire transaction is executed within the lock’s scope, i.e., acquire the lock, start the transaction, perform the business logic, commit, and finally release the lock. This guarantees that the commit happens before the lock is released.

Alternative solutions include:

Using a higher isolation level such as SERIALIZABLE to prevent overselling without explicit locks (though it may impact performance).

Programmatic transaction management to control begin, commit, and rollback manually.

Adjusting @Transactional attributes (e.g., isolation, propagation) to suit the concurrency requirements.

For distributed systems, a distributed lock (e.g., Redis or Zookeeper) should be used instead of a simple in‑process lock.

Rollback‑Only Scenario

When an inner transaction throws an exception, Spring marks the outer transaction as rollback‑only. The check defStatus.isGlobalRollbackOnly() causes the outer transaction to roll back even if the outer method completes normally.

Understanding transaction propagation (default REQUIRED) helps avoid unexpected rollbacks.

Conclusion

In the examined code, the transaction commit occurs after the method finishes, which means the lock is released before the commit. Properly nesting the lock and transaction, or using a stricter isolation level, resolves the overselling problem.

Popular past articles
Popular past articles
Transaction start timing diagram
Transaction start timing diagram
Concurrent execution result showing overselling
Concurrent execution result showing overselling
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.

DebuggingtransactionspringmysqlLock
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.