MyBatis事务不生效的主因是@Transactional未作用于public方法,因Spring AOP代理仅拦截public方法;还需检查@MapperScan配置、避免同类直接调用、合理选用传播行为如REQUIRED或REQUIRES_NEW。

MyBatis事务不生效?先看@Transactional是否作用在public方法上
Spring AOP代理默认只拦截public方法,如果把@Transactional加在private、protected或包级方法上,事务根本不会启动——连日志都不会打。这不是MyBatis的问题,是Spring代理机制的硬限制。
实操建议:
- 检查目标方法是否为
public,且未被final修饰 - 避免在同一个类内调用本类的
@Transactional方法(比如serviceA.method1()内部直接调this.method2()),这会绕过代理,事务失效 - 若必须同类调用,考虑注入自身Bean:
@Autowired private ServiceA self;,再用self.method2()
Propagation.REQUIRED vs Propagation.REQUIRES_NEW:嵌套调用时行为差异极大
Propagation.REQUIRED(默认)复用当前事务,没有就新建;Propagation.REQUIRES_NEW则总是挂起当前事务、开新事务。两者在异常回滚、数据库连接、日志一致性上表现完全不同。
常见错误现象:
- 用
REQUIRED时,内层方法抛异常,外层没捕获,整个事务回滚——但你可能只想回滚内层 - 用
REQUIRES_NEW时,内层事务提交后,外层事务仍可回滚,导致“已提交却消失”的数据幻觉 - 内层
REQUIRES_NEW方法里查不到外层刚insert但未commit的数据(隔离级别影响)
示例场景:订单创建后发通知,通知失败不应导致订单回滚:
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderMapper.insert(order);
notifyService.sendAsync(order); // 这里用REQUIRES_NEW更安全
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendAsync(Order order) {
// 即使这里异常,订单数据不受影响
notificationMapper.insert(new Notification(order));
}
MyBatis执行SQL后事务未提交?检查是否漏了@MapperScan或SqlSession生命周期
事务由Spring管理,但MyBatis操作需通过Spring托管的SqlSession才能参与事务。如果手动new SqlSession,或Mapper接口没被@MapperScan扫描到,SQL会走自动提交模式(auto-commit=true),@Transactional完全无效。
实操建议:
- 确认配置类或启动类上有
@MapperScan("com.example.mapper"),路径要精确匹配 - 不要在代码里写
sqlSessionFactory.openSession(),全部使用@Mapper接口注入 - 检查Mapper接口方法是否返回
void或受影响行数——返回List或Optional不影响事务,但若误配了ResultMap导致查询失败,可能掩盖事务问题
Propagation.SUPPORTS和Propagation.NOT_SUPPORTED:读多写少场景下的轻量选择
这两个传播行为常被忽略,但在高频查询、缓存预热、日志记录等只读场景下能显著降低事务开销。它们不强制开启事务,也就避免了不必要的连接占用和锁竞争。
关键区别:
-
SUPPORTS:有事务就加入,没事务就非事务执行——适合“可有可无”的读操作,如统计报表预加载 -
NOT_SUPPORTED:主动挂起当前事务,以非事务方式执行——适合明确不能进事务的操作,如写本地日志文件、调用外部HTTP接口 - 两者都要求方法内不能出现
INSERT/UPDATE/DELETE,否则可能报Cannot execute update in a read-only transaction
注意:MySQL默认隔离级别是REPEATABLE READ,即使NOT_SUPPORTED,查询仍可能触发间隙锁——这不是传播行为的问题,而是存储引擎层面的机制。










