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的事务逻辑。

这种问题,可以通过下面的方式解决:

  1. 启动类新增注解 @EnableAspectJAutoProxy(exposeProxy = true)
  2. 内部调用改回代理对象调用,通过 AopContext.currentProxy() 获取代理对象(强制转化成当前对象)。

实际上,并不是所有内部调用都会失效,还要看调用的方法有没有开启事务,简单总结如下:

a()调用b()时:

  1. a开启事务,b开启事务:ab在同一事物(a的事务)内执行
  2. a不开启事务,b开启事务:ab都不在事务内执行
  3. a开启事务,b不开启事务:ab在同一事物(a的事务)内执行
  4. 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管理。

这种情况可以通过下面的方式解决:

  1. 改由DataSourceUtils.getConnection(dataSource)的方式获取Connection
  2. 改用JdbcTemplate代替Connection的方式

3. 其他情况

还有一些情况会导致@Transactional失效的场景:

  1. 非public方法
  2. 抛出异常为受检异常(Exception)
  3. 数据库引擎不支持事务
posted on 2023-02-11 14:56  $$X$$  阅读(127)  评论(0)    收藏  举报