Why Does @Transactional Commit After Unlock? Uncovering Spring’s Transaction Timing
This article explains how Spring’s @Transactional works with locks, why the transaction may commit after the unlock operation, and provides debugging techniques to trace transaction start, commit, and rollback points in high‑concurrency scenarios.
Transaction Opening Timing
Spring begins a transaction by switching the JDBC connection to manual commit, which only prepares the transaction; the actual start happens when the first SQL statement is executed, usually after acquiring a lock.
In a typical @Transactional method that reduces inventory and creates an order, the method is wrapped with a lock to ensure only one thread can execute the critical section at a time.
If the transaction commits before the lock is released, the inventory decrement is safely persisted before another thread can read the stale value, preventing overselling.
However, if the transaction commits after unlock, another thread may read the unchanged inventory and also proceed to create an order, leading to overselling.
Spring’s transaction manager logs the switch to manual commit:
Switching JDBC Connection [...] to manual commit
This log corresponds to the code line con.setAutoCommit(false), which only disables auto‑commit; the transaction is not yet active. con.setAutoCommit(false) The real start occurs when the first SQL (e.g., a SELECT on the inventory table) is executed after the lock is acquired.
When an exception is thrown, Spring decides whether to roll back or commit based on the exception type. By default, RuntimeException and Error trigger a rollback.
The decision logic resides in
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn, which checks the exception against RollbackRuleAttribute and NoRollbackRuleAttribute entries parsed from @Transactional annotations.
During normal execution, the commit occurs in
org.springframework.transaction.support.AbstractPlatformTransactionManager#commit. Before committing, Spring checks if the transaction has been marked rollback‑only (e.g., due to an exception in a nested transaction).
Debugging tips:
Set a breakpoint at
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBeginto see when the connection is set to manual commit.
Breakpoint at
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransactionto observe the transaction boundaries.
Breakpoint at com.mysql.cj.protocol.a.NativeProtocol#sendQueryString to catch the actual SQL commit statements.
To ensure the transaction commits before the lock is released, place the entire transactional logic inside the locked section, or use a higher isolation level such as SERIALIZABLE to avoid overselling without explicit locks.
In summary, the transaction’s real start is after the lock acquisition, and its commit must occur before unlocking to guarantee atomicity in high‑concurrency scenarios.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
