本文正在参加「金石计划 . 瓜分6万现金大奖」

日积月累,积习沉舟

1、private、final、static 方法

@Transactional 注解标示的方法的拜访权限有必要是 public;

@Transactional 注解标示的方法不能被 final、static 润饰,被标示的方法有必要是可掩盖的。这是由于业务底层运用的是 aop,而 aop 运用的是署理模式。署理模式生成的署理类无法重写被 final、static 润饰的方法。而 private 方法对子类不可见。

Spring 事务失效的场景
Spring 事务失效的场景
Spring 事务失效的场景

2、非署理目标调用

非署理目标调用调用业务方法,业务方法会失效。如下:

public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    reduce();
}
@Transactional
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这种状况两个方法的操作都不会进行回滚。reduce() 方法相当于 this.reduce(),而 this 不是署理目标,所以 reduce 方法业务失效。

Spring 业务是根据 AOP 实现的,被 @Transactional 标示后,产生的目标是署理目标。业务的提交、回滚便是署理逻辑,想运用到署理逻辑需要运用署理目标。

解决方案也有几种,比方:

  • ① 将业务方法移动到别的一个类中、
  • ② 在本类中注入自己、
  • ③ 运用 @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()

小杰这里运用第二种方式。

@Autowired
private TestServiceImpl serviceImpl;
public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    serviceImpl.reduce();
}
@Transactional
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这样 reduce() 方法就不会呈现业务失效,所以发生反常,会进行回滚。但 transfer 就不是个业务方法,所以不会回滚。

3、将反常处理掉

 @Transactional
 public void transfer() {
     String sql = "update `test`  set money = money + 100 where id = 1;";
     jdbcTemplate.update(sql);
     //serviceImpl.reduce();
     try {
         int i = 1 /0;
     } catch (Exception e) {
     }
 }

4、抛出的反常不在回滚范围内

 @Transactional
 public void transfer() throws Exception {
     String sql = "update `test`  set money = money + 100 where id = 1;";
     jdbcTemplate.update(sql);
     //serviceImpl.reduce();
     try {
         int i = 1 /0;
     } catch (Exception e) {
         throw new Exception(e);
     }
 }

默许状况下,Spring 业务只有遇到 RuntimeException 以及 Error 时才会回滚,在遇到检查型反常时是不会回滚的,比方 IOException、TimeoutException。所以,一般状况下都需要运用 rollbackFor参数指定回滚反常类,比方:@Transactional(rollbackFor = Exception.class)

5、运用过错的传达行为

@Transactional(rollbackFor = Exception.class)
public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    serviceImpl.reduce();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这种写法会使 reduce 方法业务失效,呈现反常不会回滚。这是由于运用了 NOT_SUPPORTED 的传达行为,该行为的特性是:以非业务方式运转,如果当前存在业务,则把当前业务挂起。而 transfer 方法会进行业务回滚,这是由于 reduce 方法的反常会往上抛,被 transfer 感知到,进行了业务回滚。

6、多线程调用

@Transactional(rollbackFor = Exception.class)
public void transfer() throws InterruptedException {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    new Thread(() ->{
        serviceImpl.reduce(jdbcTemplate);
    }).start();
    Thread.sleep(1000);
}
@Transactional(rollbackFor = Exception.class)
public void reduce(JdbcTemplate jdbcTemplate) {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

从示例代码中,能够看到业务方法 transfer 调用了业务方法 reduce,而 reduce 方法是开启了一个新线程调用的。这样会导致 reduce 方法不会加入到 transfer 业务中,reduce 方法会从头创建一个新业务。 这是由于 Spring 的业务是经过数据库衔接来创建的,同一个业务,只能用同一个数据库衔接。而多线程场景下,拿到的数据库衔接是不一样的,即会导致获取到的不同业务。既然是两个业务,则没方法进行一致回滚。

7、数据库引擎不支持业务

比方 Mysql 的 MyISAM引擎就不支持业务。

8、署理类过早实例化

@Service
public class TestServiceImpl implements BeanPostProcessor, Ordered {
    @Autowired
    private TestServiceImpl serviceImpl;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Transactional(rollbackFor = Exception.class)
    public void transfer()  {
        String sql = "update `test`  set money = money + 100 where id = 1;";
        jdbcTemplate.update(sql);
        serviceImpl.reduce(jdbcTemplate);
    }
    private void reduce(JdbcTemplate jdbcTemplate) {
        String sql = "update `test`  set money = money - 100 where id = 2;";
        jdbcTemplate.update(sql);
        int i = 1 / 0;
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

当署理类的实例化早于 AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器进行AOP增强。

上面 8 种业务失效场景中,需要我们往常留意的只有 2、3、4、5。

  • 如你对本文有疑问或本文有过错之处,欢迎谈论留言指出。如觉得本文对你有所协助,欢迎点赞和关注。