事务控制的3种方式

  1. 编程式事务:直接在代码里手动开启事务,手动提交,手动回滚。优点就是可以灵活控制,缺点就是太麻烦了,太多重复的代码了
  2. 声明式事务:就是使用Spring Aop配置事务,这种方式简化了编码。需要注意的是切入点表达式一定要写正确。
  3. 注解事务:直接在Service层的方法上面加上@Transactional注解,最简单方便的方式。

伪代码

排序往后的方法报错,导致排序前的方法不回滚,如下:updataFlag方法出错,analyseRedBallanalyseBlueBall 方法不会回滚

public void statistics() {
// 三个子方法中有操作数据库方法
analyseRedBall(periods, redBalls);
analyseBlueBall(periods, blueBalls);

updataFlag(periods);
}

为什么不会滚呢

Spring默认情况下是捕获到方法的RuntimeException异常,也就是说只要属于RuntimeException异常或及其子类都能回滚。不属于运行时异常时,事务不回滚的。

解决方案

声明式事务

确保切入点表达式书写正确,如在配置里面添加rollback-for

<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/> 

注解事务

使用位置

  1. 类上:该类的所有 public 方法将都具有务属性
  2. 方法上:只能应用到 public 方法上,这是由Spring AOP的本质决定的,如果在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,将被忽略,也不会抛出任何异常。
  3. 接口、接口方法上:接口实现类或接口实现方法可继承事务属性,Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效

基本用法

将Spring默认的RuntimeException异常修改为Exception异常,可以保证任何异常都可以回滚。

@Transactional(rollbackFor = Exception.class)

或指定多个异常

@Transactional(rollbackFor = {Exception.class, RuntimeException.class})

如上述实例修改为

@Transactional(rollbackFor = Exception.class)
public void statistics() {
// 在statistics 父方法上使用事务注解,即可保证发生异常后,三个子方法事务全部回滚(子方法上不再需要写上事务注解@Transactional)
analyseRedBall(periods, redBalls);
analyseBlueBall(periods, blueBalls);

updataFlag(periods);
}

异常抛出

在catch语句中抛出异常,以便让Aop捕获异常执行回滚事务,如下伪代码

@Transactional(rollbackFor = Exception.class)
public void statistics() {
analyseRedBall(periods, redBalls);
analyseBlueBall(periods, blueBalls);

updataFlag(periods);
}

public void analyseRedBall(List<Period> periods, List<RedBall> redBalls) {
try {
// ...逻辑代码
} catch (Exception e) {
throw new RuntimeException();
}
}

手动事务

配合事务注解@Transactional,手动处理事务回滚

设置回滚代码

catch语句中设置回滚代码来实现回滚,此方法在抛出异常后也能return 返回值,适合需要拿到返回值的场景

 @Transactional(rollbackFor = Exception.class)
public boolean statistics() {
try {
analyseRedBall(periods, redBalls);
analyseBlueBall(periods, blueBalls);

updataFlag(periods);
} catch (Exception e) {
// 加上此句,则不再需要手动抛出指定异常
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return fales;
}
return true;
}

设置回滚点

// 方法返回的是object类型,o 为回滚点变量名
Object o = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
// ...逻辑代码
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(o);

存储引擎

到此之前,代码无误,那为什么 Spring 或 SpringBoot 的事务回滚还是没有任何效果呢?

数据库肯定是MySQL ,那表的存储引擎,也要支持事务安全才行,最重要,也是最多人忽视的地方;InnoDB 和 BDB 提供事务安全表,其他存储引擎都是非事务安全表

微信图片_20220427163024.png

注:Oracleb不存在存储引擎的概念,数据处理大致可以分成两大类:联机事务处理OLTP(on-line transaction processing)、联机分析处理OLAP(On-Line Analytical Processing)。

  • OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作
  • OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等

总结

@Transactional和@Transactional(rollbackFor = Exception.class)的区别

  • @Transactional只能回滚RuntimeExceptionRuntimeException的子类抛出的异常,不能回滚Exception异常
  • 如果需要支持回滚Exception异常请用@Transactional(rollbackFor = Exception.class)
  • 增删改建议使用@Transactional(rollbackFor = Exception.class)

@Transactional(rollbackFor = Exception.class)的失效场景

  • 不是public修饰
  • try...catch...捕获了异常(没在catch里面手动抛出异常)
  • 没有加@Service(没被 Spring 管理)