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 方法事务失效。
解决方案也有几种,比如:将事务方法移动到另外一个类中、在本类中注入自己、使用 @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。
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。