Spring的@Transactional在某些情况下会出现失效的问题,简单了整理比较常见的部分。
1. 内部调用
Spring的@Transactional事务管理本质上通过AOP来实现事务管理,在对象内部方法调用时,可能出现事务失效。
如下面这种情况:
@Transactional
public void insert(int div) {
jdbcTemplate.update("insert into tb_user(name) values(?)", "user.A.");
System.out.println(1 / div);
}
public void call(int div) {
insert(div);
}
当外部调用call方法,如:userAService.call(0); insert会发生by zero的RuntimeException,事务是不会回滚的,也就是说,@Transactional失效了。
正常情况下,但业务方法出现非受检的异常时(如RuntimeException、Error),Spring会回滚事务。
但在出现当前对象(同一个类的同一对象)内部方法调用时,实际上call中调用insert的并非AOP代理对象,所以没有执行AOP的事务逻辑。
这种问题,可以通过下面的方式解决:
- 启动类新增注解
@EnableAspectJAutoProxy(exposeProxy = true) - 内部调用改回代理对象调用,通过
AopContext.currentProxy()获取代理对象(强制转化成当前对象)。
实际上,并不是所有内部调用都会失效,还要看调用的方法有没有开启事务,简单总结如下:
当a()调用b()时:
- a开启事务,b开启事务:ab在同一事物(a的事务)内执行
- a不开启事务,b开启事务:ab都不在事务内执行
- a开启事务,b不开启事务:ab在同一事物(a的事务)内执行
- a不开启事务,b不开启事务:ab都不在事务内执行
2. 直接获取Connection
如果代码中直接通过数据源获取Connection并交给业务方法执行数据库业务,那么@Transactional也会失效,如:
@Transactional
public void insertByConn(int div) throws SQLException {
Connection conn = dataSource.getConnection(); // no close
PreparedStatement ps = conn.prepareStatement("insert into tb_user(name) values(?)");
ps.setString(1, "user.");
ps.executeUpdate();
System.out.println(1 / div);
}
像上面这种情况,div传0,出现by zero异常,但是由于Connection是直接通过DataSource获取的,尽管没有关闭,但还是事务还是不会回滚,因为这个Connection并没有交由Spring管理。
这种情况可以通过下面的方式解决:
- 改由
DataSourceUtils.getConnection(dataSource)的方式获取Connection - 改用JdbcTemplate代替Connection的方式
3. 其他情况
还有一些情况会导致@Transactional失效的场景:
- 非public方法
- 抛出异常为受检异常(Exception)
- 数据库引擎不支持事务
浙公网安备 33010602011771号