
spring 声明式事务(@transactional)看似简单,实则暗藏多个易被忽视的失效场景:自调用绕过代理、异常类型不匹配、方法非 public、多数据源未指定管理器、传播行为误用及线程上下文丢失——任一疏漏均会导致“事务不回滚”“静默提交”或“transactionrequiredexception”等致命问题。
spring 声明式事务(@transactional)看似简单,实则暗藏多个易被忽视的失效场景:自调用绕过代理、异常类型不匹配、方法非 public、多数据源未指定管理器、传播行为误用及线程上下文丢失——任一疏漏均会导致“事务不回滚”“静默提交”或“transactionrequiredexception”等致命问题。
在 Spring 应用中,@Transactional 是保障数据一致性的核心机制,但其底层依赖 AOP 动态代理 + ThreadLocal 事务上下文绑定。一旦代理未生效或上下文断裂,注解即形同虚设——正如你遇到的 TransactionRequiredException: Executing an update/delete query 错误:首次调用时 TransactionSynchronizationManager.isActualTransactionActive() 返回 true,第二次却为 false,这明确指向 事务上下文未正确延续,而非 SQL 或 JPA 配置问题。
? 根本原因定位:自调用 + AspectJ 代理失效(高概率)
从你的调用链可清晰还原问题路径:
// Action 类(非 Spring 管理的普通类?或未被代理的 Bean)
public String SuUser() {
// ... 获取用户
userManager.upgradeToSuperUser(...); // ⚠️ 关键:此处调用是否经过 Spring 代理?
}
而 upgradeToSuperUser() 定义在 UserManager 中,并标注了 @Transactional(propagation = Propagation.REQUIRED)。若 SuUser() 所在的 action 类 未被 Spring 容器管理(如通过 new Action() 实例化),或 userManager 是通过 @Autowired 注入但 SuUser() 方法本身不在 Spring 代理对象内执行,则整个调用链将脱离事务拦截器。
更关键的是:你启用了 <annotation-driven mode="aspectj"></annotation-driven>,这意味着事务织入依赖 AspectJ 编译期/加载时织入(LTW),而非运行时 Spring AOP 代理。但你的 aspectj-maven-plugin 配置存在严重隐患:
- 使用了过时的
aspectj-maven-plugin:1.7(已停止维护),且未启用weaveDependencies或weaveMain; -
outxml=true仅生成 aop.xml,但若未配置LoadTimeWeaver或 JVM-javaagent,LTW 不会生效; -
proxy-target-class="true"在mode="aspectj"下完全无效(此属性仅对mode="proxy"生效);
结果是:你以为启用了 AspectJ 织入,实际仍走 Spring AOP 代理路径,而 AOP 代理无法拦截同一 Bean 内部的 this. 调用 —— 这正是你第二次调用时事务上下文丢失的根源。
✅ 正确修复方案(三步落地)
1️⃣ 确保调用方处于 Spring 代理上下文
避免在非 Spring Bean(如 Struts Action、Servlet)中直接调用事务方法。应确保 SuUser() 所在类是 @Service 或 @Controller,并通过 Spring 容器注入:
@Controller
public class UserAction {
@Autowired
private UserManager userManager; // ✅ Spring 管理的代理 Bean
public String SuUser() {
// ... 业务逻辑
userManager.upgradeToSuperUser(userToBeMadeSuperUser, currentSuperUser);
return "success";
}
}
⚠️ 若必须在非容器类中调用,请通过
ApplicationContext.getBean(UserManager.class)获取代理实例,禁止使用 new 或静态引用。
2️⃣ 彻底解决 AspectJ 配置缺陷(推荐升级方案)
鉴于你正从 Spring 3 升级至 Spring 5,强烈建议弃用 AspectJ LTW,回归标准 Spring AOP(更稳定、易调试):
<!-- appContext.xml --> <!-- 移除 mode="aspectj",改用默认 proxy 模式 --> <annotation-driven transaction-manager="transactionManager"></annotation-driven>
同时删除 aspectj-maven-plugin 相关配置,确保:
-
spring-aspects依赖版本与 Spring 5 兼容(如5.3.37); -
@EnableTransactionManagement(Java Config)或<annotation-driven></annotation-driven>已正确声明; -
UserManager类为@Service,且事务方法为public。
3️⃣ 强化事务方法健壮性(防御式编码)
即使代理生效,也需规避常见陷阱。修正后的 upgradeToSuperUser 示例:
@Service
public class UserManagerImpl implements UserManager {
@PersistenceContext // ✅ 推荐替代 @Autowired EntityManager
private EntityManager entityManager;
@Override
@Transactional(propagation = Propagation.REQUIRED,
rollbackFor = Exception.class) // 显式回滚所有异常
public void upgradeToSuperUser(UserBO userToBeMadeSuperUser, UserBO loggedInUser) {
System.out.println("Tx Active: " + TransactionSynchronizationManager.isActualTransactionActive());
// 使用 JPQL 替代 Native Query(更安全,支持一级缓存)
String jpql1 = "UPDATE User u SET u.role = 'SUPERUSER' WHERE u.id = :id";
entityManager.createQuery(jpql1)
.setParameter("id", userToBeMadeSuperUser.getId())
.executeUpdate();
String jpql2 = "UPDATE User u SET u.role = 'TEACHER' WHERE u.id = :id";
entityManager.createQuery(jpql2)
.setParameter("id", loggedInUser.getId())
.executeUpdate();
// ✅ 关键:不捕获异常!让@Transactional 自动回滚
}
}
? 关键注意事项总结
| 场景 | 验证方式 | 修复要点 |
|---|---|---|
| 自调用失效 | 调试时观察 this.getClass() 是否为 $$EnhancerBySpringCGLIB
|
拆分方法到不同 Service,或通过 @Autowired 注入自身 Bean |
| 非 public 方法 | 检查方法修饰符 | 必须为 public,private/protected/包级访问均失效 |
| 异常未回滚 | 抛出 IOException 后数据仍提交 |
添加 rollbackFor = Exception.class
|
| 多数据源事务 | 操作非主库时事务不生效 |
@Transactional("secondaryTransactionManager") 显式指定 |
| 异步线程丢失上下文 |
@Async 方法内事务不生效 |
使用 TransactionSynchronizationManager 手动传播,或改用 @Transactional + TaskExecutor 配置 |
? 终极排查口诀:
“一看调用源(是否代理)、二查方法签名(是否 public)、三验异常类型(是否在 rollbackFor 范围)、四核事务管理器(是否匹配数据源)、五断线程上下文(是否跨线程/异步)”。
事务不是魔法,而是精密的上下文传递机制。每一次“不生效”,都是对 Spring AOP 和事务传播原理的一次深度校验。严谨对待代理边界与异常契约,才能让 @Transactional 真正成为数据一致性的坚实盾牌。










