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.

macrozheng
macrozheng
macrozheng
Why Does @Transactional Commit After Unlock? Uncovering Spring’s Transaction Timing

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#doBegin

to see when the connection is set to manual commit.

Breakpoint at

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

to 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.

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.

JavatransactiondatabaseconcurrencyspringLock
macrozheng
Written by

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.

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.