本文正在参加「金石计划 . 瓜分6万现金大奖」
日积月累,积习沉舟
1、private、final、static 方法
被 @Transactional
注解标示的方法的拜访权限有必要是 public;
被 @Transactional
注解标示的方法不能被 final、static 润饰,被标示的方法有必要是可掩盖的。这是由于业务底层运用的是 aop,而 aop 运用的是署理模式。署理模式生成的署理类无法重写被 final、static 润饰的方法。而 private 方法对子类不可见。
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。
- 如你对本文有疑问或本文有过错之处,欢迎谈论留言指出。如觉得本文对你有所协助,欢迎点赞和关注。