Common Scenarios Where Spring Transactions Fail and How to Fix Them
This article explains why Spring @Transactional may become ineffective in various situations—such as wrong method visibility, final or static modifiers, internal method calls, missing bean registration, multithreading, unsupported table engines, misconfigured propagation, swallowed exceptions, and improper rollback settings—and provides practical solutions for each case.
Preface
Spring transaction management is a core skill for Java developers, but misuse can easily cause transactions to become ineffective, leading to data inconsistency.
1. Transaction Not Effective
1.1 Access Modifier Issue
If a transactional method is declared private, protected or package‑private, Spring will not create a proxy for it because
AbstractFallbackTransactionAttributeSource.computeTransactionAttributeonly returns a TransactionAttribute for public methods.
@Service
public class UserService {
@Transactional
private void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}1.2 Method Declared final
A final method cannot be overridden by the CGLIB proxy, so the transactional advice cannot be applied.
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}Note: static methods also cannot be proxied.
1.3 Internal Method Calls
Calling another transactional method from the same class (e.g., this.updateStatus()) bypasses the proxy, so the second method runs without a transaction.
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}Solutions include extracting the called method to another @Service bean, injecting the bean into itself, or using AopContext.currentProxy() to obtain the proxy.
1.3.1 Add a New Service
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Service
public class ServiceB {
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}1.3.2 Inject Self
@Service
public class ServiceA {
@Autowired
private ServiceA serviceA;
public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}1.3.3 Use AopContext
@Service
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA) AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}1.4 Bean Not Managed by Spring
If a class lacks @Service, @Component, etc., Spring will not create a bean, and any @Transactional annotation on its methods will be ignored.
//@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}1.5 Multithreaded Calls
Transactional code executed in a new thread does not share the same database connection, so the outer transaction cannot roll back the inner thread’s work.
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("save role data");
}
}1.6 Table Engine Does Not Support Transactions
MyISAM tables cannot participate in transactions; using InnoDB (or another transactional engine) is required for multi‑table operations.
CREATE TABLE `category` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`one_category` VARCHAR(20) COLLATE utf8mb4_bin DEFAULT NULL,
...
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;1.7 Transaction Not Enabled
In a plain Spring project you must declare a DataSourceTransactionManager and appropriate aop configuration; Spring Boot does this automatically.
<!-- Configure transaction manager -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>2. Transaction Does Not Roll Back
2.1 Wrong Propagation Setting
Using Propagation.NEVER disables transaction creation, causing the method to run without a transaction.
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}2.2 Swallowing Exceptions
If a transactional method catches exceptions and does not re‑throw them, Spring assumes the operation succeeded and will not roll back.
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}2.3 Throwing Non‑Runtime Exceptions
By default Spring rolls back only on RuntimeException or Error. Throwing a checked Exception will not trigger a rollback unless configured.
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}2.4 Misconfigured rollbackFor
If rollbackFor is set to a custom exception that never occurs, the transaction will not roll back for the actual database exceptions.
@Service
public class UserService {
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}2.5 Nested Transaction Rollback Propagation
When a nested transaction throws an exception that propagates outward, the outer transaction also rolls back. To isolate the inner rollback, catch the exception inside the outer method.
@Service
public class UserService {
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
// ...
}
}3. Other Topics
3.1 Large Transaction Problems
Annotating whole service methods with @Transactional can unintentionally include many read‑only queries, leading to long‑running transactions and performance issues. It is better to limit the transactional scope to the exact statements that modify data.
3.2 Programmatic Transactions
Spring also offers programmatic transaction control via TransactionTemplate, which avoids AOP proxy pitfalls and gives finer‑grained control.
@Autowired
private TransactionTemplate transactionTemplate;
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute(status -> {
addData1();
updateData2();
return Boolean.TRUE;
});
}It is advisable to prefer programmatic transactions for complex scenarios, while simple, stable operations can still safely use @Transactional.
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.
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
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.
