Java实用注解篇Transactional-事务失效的场景深度解析
Java实用注解篇:@Transactional 事务失效的场景深度解析
前言
在使用
@Transactional
时,很多开发者都会遇到一个常见困惑:
明明加了事务注解,但事务却没有生效,数据库操作仍然被提交了!
这是因为事务机制的触发有一些 前提条件 ,只要触碰到事务失效的“雷区”,就会让事务变成“摆设”。接下来,我们来详细剖析几种常见的 事务失效场景 以及 解决方案 !
✅ 1️⃣ 同一个类中方法直接调用,事务失效
问题复现:
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Transactional
    public void methodA() {
        userRepository.save(new User("UserA"));
        methodB(); // 内部调用,不触发事务
    }
    @Transactional
    public void methodB() {
        userRepository.save(new User("UserB"));
        throw new RuntimeException("Test rollback"); // 期望回滚
    }
}执行结果:
- UserA 会被插入数据库
 - UserB 没有插入,因为抛出了异常
 - 🚨 事务没有回滚!
 
为什么会失效?
Spring 的事务是基于 AOP 代理机制 实现的,而代理对象只有在 外部调用 时才会触发拦截器逻辑,从而开启事务。
同一个类内的方法调用不会经过代理对象,而是通过
this.methodB()
调用的,因此不会触发事务机制!
✅ 解决方案:
方案一:通过代理对象调用事务方法
@Autowired
private UserService userService;
@Transactional
public void methodA() {
    userRepository.save(new User("UserA"));
    userService.methodB(); // 通过代理对象调用,触发事务
}方案二:使用
AopContext
获取当前代理对象
Spring 提供了
AopContext
工具类,可以获取当前代理对象:
import org.springframework.aop.framework.AopContext;
@Transactional
public void methodA() {
    userRepository.save(new User("UserA"));
    ((UserService) AopContext.currentProxy()).methodB();
}注意:
- 需要在配置类中开启
暴露代理对象功能
才能使用
AopContext: 
@EnableAspectJAutoProxy(exposeProxy = true)✅ 2️⃣ 非 public 方法上的 @Transactional 注解无效
问题复现:
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Transactional
    private void addUser() {
        userRepository.save(new User("PrivateUser"));
        throw new RuntimeException("Test rollback");
    }
}执行结果:
- 数据成功插入,事务 没有回滚 !
 
为什么会失效?
Spring 的事务是基于
动态代理机制
,只有
public
方法
才会被代理,
private
、
protected
、
default
修饰的方法不会被代理
,因此不会触发事务机制。
✅ 解决方案:
改为
public
方法:
@Transactional
public void addUser() {
    userRepository.save(new User("PublicUser"));
    throw new RuntimeException("Test rollback");
}✅ 3️⃣ 数据库引擎不支持事务
问题复现:
如果你的数据库表使用的是 MySQL 的 MyISAM 引擎 而不是 InnoDB ,事务机制是不会生效的!
检查表引擎:
SHOW TABLE STATUS WHERE Name = 'your_table';如果看到
Engine=MyISAM
:
+------------+--------+ ...
| Name       | Engine | ...
+------------+--------+ ...
| user_table | MyISAM | ...
+------------+--------+ ...为什么会失效?
- MyISAM 是 MySQL 的早期存储引擎,不支持事务回滚、外键等特性。
 - InnoDB 才是支持事务、行级锁等功能的存储引擎。
 
✅ 解决方案:
把表引擎改为 InnoDB:
ALTER TABLE your_table ENGINE=InnoDB;查看所有 MyISAM 表:
SELECT table_schema, table_name, engine
FROM information_schema.tables
WHERE engine = 'MyISAM';✅ 4️⃣ 捕获了异常,导致事务无法回滚
问题复现:
@Transactional
public void addUser() {
    try {
        userRepository.save(new User("TryCatchUser"));
        int result = 1 / 0; // 抛出 ArithmeticException
    } catch (Exception e) {
        // 异常被捕获
        System.out.println("Exception caught: " + e.getMessage());
    }
}执行结果:
- 数据被插入
 - 🚨 事务没有回滚!
 
为什么会失效?
- Spring 默认只有
未捕获的运行时异常
(继承自
RuntimeException)才会触发回滚。 - 受检异常
(如
IOException)或者 被捕获的异常 不会触发事务回滚! 
✅ 解决方案:
方案一:手动抛出异常,让 Spring 感知到事务需要回滚:
@Transactional
public void addUser() {
    try {
        userRepository.save(new User("User"));
        int result = 1 / 0;
    } catch (Exception e) {
        throw new RuntimeException(e); // 抛出运行时异常触发回滚
    }
}方案二:使用
rollbackFor
明确指定哪些异常触发回滚:
@Transactional(rollbackFor = Exception.class)
public void addUser() {
    try {
        userRepository.save(new User("User"));
        int result = 1 / 0;
    } catch (Exception e) {
        // 异常依然被捕获,但事务仍然会回滚
        System.out.println("Caught exception, but rollback still happens!");
    }
}✅ 5️⃣ 多线程环境下事务失效
问题复现:
@Transactional
public void addUser() {
    new Thread(() -> {
        userRepository.save(new User("AsyncUser"));
        throw new RuntimeException("Test rollback");
    }).start();
}执行结果:
- 数据被插入
 - 🚨 事务没有回滚!
 
为什么会失效?
Spring 的事务是
线程绑定的
(基于
ThreadLocal
实现),
新线程不会继承原线程的事务上下文 ,因此新线程无法感知到原线程的事务边界。
✅ 解决方案:
使用
@Async
配合事务:
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Async
    @Transactional
    public void addUserAsync() {
        userRepository.save(new User("AsyncUser"));
        throw new RuntimeException("Test rollback");
    }
}注意:
- 需要在启动类上加上
@EnableAsync: 
@SpringBootApplication
@EnableAsync
public class Application {
}✅ 6️⃣ 数据库连接的自动提交未关闭
问题复现:
如果你的数据库连接池配置了 自动提交 ,那么事务控制会被绕过!
可能的配置:
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test_db
    username: root
    password: 123456
    hikari:
      auto-commit: true  # 🚨自动提交为true为什么会失效?
- Spring 事务会通过
DataSource
获取连接,如果连接的
autoCommit=true,每次 SQL 执行都会立刻提交。 - 事务注解控制的提交/回滚行为被绕过 。
 
✅ 解决方案:
- 确保数据库连接池关闭自动提交 :
 
spring:
  datasource:
    hikari:
      auto-commit: false- 或者手动关闭自动提交:
 
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);✅ 7️⃣ 代理对象被绕过(比如使用 this 关键字)
问题复现:
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Transactional
    public void outerMethod() {
        userRepository.save(new User("OuterMethod"));
        this.innerMethod(); // 🚨不会触发事务
    }
    @Transactional
    public void innerMethod() {
        userRepository.save(new User("InnerMethod"));
        throw new RuntimeException("Test rollback");
    }
}为什么会失效?
- Spring 的事务是基于代理对象实现的
,
this.innerMethod()是直接调用自身方法,绕过了代理对象。 - 事务拦截器不会生效 ,因此内部方法不会走事务逻辑。
 
✅ 解决方案:
- 通过代理对象调用事务方法:
 
@Autowired
private UserService userService;
public void outerMethod() {
    userRepository.save(new User("OuterMethod"));
    userService.innerMethod(); // 🚀触发事务
}✅ 8️⃣ 嵌套事务设置不当
问题复现:
当你有嵌套事务时,如果 传播机制(Propagation) 配置不当,也可能造成事务失效。
例如:
@Transactional
public void outerMethod() {
    userRepository.save(new User("Outer"));
    innerMethod();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
    userRepository.save(new User("Inner"));
    throw new RuntimeException("Test rollback");
}为什么会失效?
@Transactional(propagation = Propagation.NESTED)Propagation.REQUIRES_NEW表示开启一个新的事务,外层事务和内层事务是独立的 ,即使内层回滚,外层不会受影响。
所以
Outer数据仍然会被提交!
✅ 解决方案:
- 如果你期望事务统一回滚,改成默认的
Propagation.REQUIRED: 
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
    userRepository.save(new User("Inner"));
    throw new RuntimeException("Test rollback");
}- 如果你想实现嵌套事务,可以考虑
NESTED传播机制 : 
✅ 9️⃣ 事务管理器配置错误
问题复现:
当你使用了多个数据源时,如果事务管理器没有正确配置,也会造成事务失效:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}为什么会失效?
如果你的数据源和事务管理器不匹配,比如有多个数据源,但事务管理器只绑定了一个数据源,
其他数据源的事务不会生效 。
没有声明事务管理器 时,Spring 会使用 默认事务管理器 ,可能不是你想要的那个。
✅ 解决方案:
- 指定事务管理器:
 
@Transactional(transactionManager = "transactionManager")- 多数据源事务管理器配置:
 
@Bean("transactionManager1")
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
@Bean("transactionManager2")
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}✅ 🔟 被代理类不是 Spring 管理的 Bean
问题复现:
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Transactional
    public void addUser() {
        userRepository.save(new User("User"));
        throw new RuntimeException("Test rollback");
    }
}调用:
UserService userService = new UserService();
userService.addUser(); // 🚨事务失效为什么会失效?
- Spring 事务依赖于 AOP 代理 ,只有被 Spring 托管的 Bean 才会启用代理机制。
 - 手动 new 对象不会触发事务机制 。
 
✅ 解决方案:
- 让 Spring 托管 Bean:
 
@Service
public class UserService {
    ...
}- 使用
ApplicationContext获取代理对象: 
@Autowired
private ApplicationContext applicationContext;
public void addUser() {
    UserService proxy = applicationContext.getBean(UserService.class);
    proxy.addUser(); // 🚀事务生效
}✅ 1️⃣1️⃣ SpringBoot 的 @EnableTransactionManagement 未生效
问题复现:
如果你在项目中使用的是 SpringBoot,但是
@Transactional
却完全没反应,
可能是因为事务管理器没有启用!
为什么会失效?
- SpringBoot 默认开启事务管理(自动配置),但如果你自己创建了配置类,并关闭了自动配置,就可能导致事务失效。
 
✅ 解决方案:
- 显式开启事务管理器:
 
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
}- 确认事务管理器是否生效:
 
@Autowired
private PlatformTransactionManager transactionManager;
@PostConstruct
public void checkTransactionManager() {
    System.out.println("Transaction Manager: " + transactionManager);
}✅ 1️⃣2️⃣ 数据库本身的问题
最后,有时候 不是代码问题 ,而是 数据库层面的配置问题 导致事务失效:
数据库权限不足 :
如果数据库用户权限不足,无法执行
ROLLBACK操作,也会让事务无法回滚。触发器 (Trigger) :
如果表有触发器,会导致某些数据在触发器执行后直接提交,绕过事务管理。
🎯 总结:事务失效场景大全
| 事务失效场景 | 失效原因 | 解决方案 | 
|---|---|---|
| 同类方法调用 | 没有代理 | 通过代理对象调用 | 
| 非 public 方法 | 只有 public 方法才会被代理 | 改为 public | 
| 数据库引擎不支持事务 | 使用 MyISAM 而不是 InnoDB | 修改为 InnoDB | 
| 异常被捕获 | Spring 默认只回滚 RuntimeException | 显式指定 rollbackFor | 
| 多线程调用 | 新线程不会继承原线程的事务 | 使用 @Async 结合事务 | 
| 自动提交未关闭 | 数据源 autoCommit=true | 关闭自动提交 | 
| 代理对象被绕过 | this 调用自身方法 | 使用代理对象调用 | 
| 嵌套事务传播机制设置不当 | REQUIRES_NEW 导致事务隔离 | 使用 NESTED 或 REQUIRED | 
| 事务管理器配置错误 | 数据源和事务管理器不匹配 | 确保事务管理器正确绑定数据源 | 
| Bean 非 Spring 托管 | new 对象不会触发代理机制 | 让 Spring 托管 Bean | 
| @EnableTransactionManagement 未开启 | 没有启用事务管理器 | 显式开启事务管理器 | 
| 数据库层面问题 | MySQL 触发器或权限问题 | 检查数据库配置 | 
✨ 思考题:
- 如何在同一类方法调用时,确保事务能够生效?
 - 为什么捕获异常后事务不会回滚?
 - 事务和线程之间的关系是怎样的?
 
如果觉得这篇文章对你有帮助,记得点赞⭐、收藏📌、关注🚀!