SpringBoot事务回滚

SpringBoot事务回滚

在开发过程中有时会遇到,多次操作数据时,某次操作出现问题导致程序终止,但是之前的数据已经操作,需要回滚使数据复原。基于这种情况,SpringBoot提供了一个注解@Transactional,帮助回滚事务。

原理

在应用系统调用声明了@Transactional的目标方法时,Spring Framework 默认使用AOP代理,在代码运行时生成一个代理对象,根据@Transactional的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器TransactionInterceptor来使用拦截,在TransactionInterceptor拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager操作数据源DataSource提交或回滚事务。

开启事务

启动类加上@EnableTransactionManagement注解,开启事务支持。

SpringBoot默认开启事务,直接在接口、类、方法上使用@Transactional注解即可。

自动回滚

1
2
3
4
5
6
7
@Override
@Transactional
public void submit() {
success();
//假如error()会抛出异常,则success()对数据库的操作会回滚。
error();
}

手动回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
@Transactional(rollbackFor = Exception.class)
public String submit(){
success();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return "error";
}
return "success";
}

部分回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
@Transactional(rollbackFor = Exception.class)
public String submit(){
success();
//只回滚以下异常
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
exception();
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
return ApiReturnUtil.error();
}
return ApiReturnUtil.success();
}

@Transactional参数

参数名称 描述
readOnly 用于设置当前事务是否为只读事务。true表示只读,false则表示可读写,默认为false。例如:@Transactional(readOnly=true)
rollbackFor 用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。默认为非检测性异常unchecked exceptions(RuntimeException和Error)。
例如:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
transactionManager / value 多个事务管理器托管在Spring容器中时,指定事务管理器的bean名称
rollbackForClassName 用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。
例如:
指定单一异常类名称:@Transactional(rollbackForClassName=”RuntimeException”)
指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”, “Exception”})
noRollbackFor 用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。
例如:
指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName 用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。
例如:
指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)
指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”, “Exception”})
propagation 用于设置事务的传播行为。默认为REQUIRED。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED, readOnly=true)
isolation 用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。
timeout 用于设置事务的超时秒数。单位为秒,-1表示永不超时,默认为-1。例如:@Transactional(timeout=30)

@Propagation参数(事务的传播行为)

参数 描述
REQUIRED 默认的传播行为。如果有事务,则加入事务。没有事务,则新建事务
SUPPORTS 如果有事务,则加入事务。没有事务,则不用事务
MANDATORY 必须在一个已有的事务中执行。如果当前存在事务,则加入该事务,如果当前不存在事务,则抛出异常
REQUIRES_NEW 不管是否存在事务,都新建事务执行。如果已有事务,则挂起当前事务,再新建事务,新的执行完毕, 再执行老的事务
NOT_SUPPORTED 必须在一个没有的事务中执行,如果当前已有事务,则将当前事务挂起
NEVER 必须在一个没有的事务中执行,否则抛出异常IllegalTransactionStateException

@isolation参数(事务的隔离级别)

参数 描述
DEFAULT 默认的隔离级别。使用数据库默认的事务隔离级别。MYSQL默认隔离级别为REPEATABLE_READ,Oracle 默认隔离级别为READ_COMMITTED,SQLSERVER默认隔离级别为READ_COMMITTED
READ_UNCOMMITTED(读取未提交数据() 最低的隔离级别。在事务完成前,其他事务可以看到该事务所修改的数据。而在其他事务提交前,该事务也可以看到其他事务所做的修改。可能导致脏读,幻读,不可重复读
READ_COMMITTED(读取已提交数据) 大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。在该事务提交后,你就可以查看其他事务插入或更新的数据。如果其他事务修改了数据,就会看到不同的数据。可防止脏读,但幻读和不可重复读仍可以发生
REPEATABLE_READ(可重复读) 比READ_COMMITTED更严格,该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的数据集,即使其他事务修改了所查询的数据。然而如果其他事务插入了新数据,你就可以查询到该新插入的数据。可防止脏读,不可重复读,但幻读仍可能发生
SERIALIZABLE(串行化) 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。代价最大、可靠性最高的隔离级别。所有的事务都是按顺序一个接一个地执行。避免所有不安全读取

注意

  1. @Transactional注解只能被应用到public方法上,对于其它的方法,标记了@Transactional注解也不会报错,但事务无法回滚。
  2. 外部调用的公共方法A未声明事务@Transactional,方法B若是其他类的方法且声明事务,则事务由子方法B控制。
  3. 外部调用的公共方法A未声明事务@Transactional,子方法B若是本类的方法,则无论子方法B是否声明事务,事务均不会生效。(可以使用applicationContext.getBean(service.class)直接从IOC容器中将类取出来,然后再调用方法B即可,这样就能用上Spring AOP生成的代理对象)
  4. 外部调用的公共方法A声明事务@Transactional,无论子方法B是不是本类的方法或者是否声明事务,事务均由公共方法A控制。
  5. 异常被try/catch捕获而没有被抛出,则事务无法回滚。
  6. 多线程下事务管理因为线程不属于spring托管,所以事物无法回滚。
  7. 数据库要支持事务,如果是mysql,要使用innodb引擎,myisam不支持事务。
如果对您有帮助,可以打赏呦!