文章目录
- 一. 事务介绍
- 1. SpringBoot事务管理方式
- 2. 事务提交方式
- 3. 事务传播行为
- 4. 事务回滚规则
- 5. 事务常用配置
- 6. 事务注意事项
- 二. 事务实战
- 1. 添加注解将事务交给Spring控制
- 2. 手动触发事务
- 3. 指定出发事务回滚的异常类型
- 4. 自定义事务
- 5. 设置断点进行回滚
一. 事务介绍
1. SpringBoot事务管理方式
在Spring中,事务管理有两种实现方式,分别是编程式事务和声明式事务
- 编程式事务:编程式事务管理使用TransactionTemplate或者直接使用底层的Platform TransactionMannager。对于编程式事务,Spring推荐使用TransactionTemplate
- 声明式事务:建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,更快捷而且简单,推荐使用。
2. 事务提交方式
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的回滚事务。对于正常的事务管理,是一组相关的操作处于一个事务中,因此必须关闭数据库的自动提交模式。不过,Spring会将底层连接的自动提交特性设置为false。也就是在使用Spring进行事务管理的模块的时候,Spring会将是否自动提交设置为false,等价于JDBC中的connection.setAutoCommit(false)
,在执行之后再进行提交,connection.commit()
3. 事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
● TransactionDefinition.PROPAGATION_REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
● TransactionDefinition.PROPAGATION_REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
● TransactionDefinition.PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
● TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
● TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
● TransactionDefinition.PROPAGATION_MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
● TransactionDefinition.PROPAGATION_NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
4. 事务回滚规则
指示Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务。
5. 事务常用配置
● readOnly
:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true);
● rollbackFor
:该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
;
● 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
:该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
。
● isolation
:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。
● timeout
:该属性用于设置事务的超时秒数,默认值为-1表示永不超时。
6. 事务注意事项
- 要根据实际的需求来决定是否要使用事务,最好是在编码之前就考虑好,不然到以后就难以维护;
- 如果使用了事务,请务必进行事务测试,因为很多情况下以为事务是生效的,但是实际上可能未生效!
- 事务@Transactional的使用要放在类的公共(public)方法中,需要注意的是在 protected、private 方法上使用 @Transactional 注解,它也不会报错(IDEA会有提示),但事务无效。
- 事务@Transactional是不会对该方法里面的子方法生效!也就是在公共方法A声明的事务@Transactional,但是在A方法中有个子方法B和C,其中方法B进行了数据操作,但是该异常被B自己处理了,这样的话事务是不会生效的!反之B方法声明的事务@Transactional,但是公共方法A却未声明事务的话,也是不会生效的!如果想事务生效,需要将子方法的事务控制交给调用的方法,在子方法中使用rollbackFor注解指定需要回滚的异常或者将异常抛出交给调用的方法处理。一句话就是在使用事务的异常由调用者进行处理!
- 事务@Transactional由Spring控制的时候,它会在抛出异常的时候进行回滚。如果自己使用catch捕获了处理了,是不生效的,如果想生效可以进行手动回滚或者在catch里面将异常抛出,比如throw new RuntimeException();。
二. 事务实战
pringBoot在使用事务Transactional的时候,要在main方法上加上 @EnableTransactionManagement 注解开启事务声明,在使用的service层的公共方法加上 @Transactional (Spring)注解。
1. 添加注解将事务交给Spring控制
公共方法上面添加@Transactional
注解,有Spring控制事务
@Transactional
public boolean test1(User user) throws Exception {
long id = user.getId();
System.out.println("查询的数据1:" + udao.findById(id));
// 新增两次,会出现主键ID冲突,看是否可以回滚该条数据
udao.insert(user);
System.out.println("查询的数据2:" + udao.findById(id));
udao.insert(user);
return false;
}
2. 手动触发事务
在使用事务@Transactional
的时候,想自己对异常进行处理的话,那么可以进行手动回滚事务。在catch中加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
不过需要注意的是发生异常需要第一时间进行手动回滚事务,也就是要在异常抛出之前!
@Transactional
public boolean test2(User user) {
long id = user.getId();
try {
System.out.println("查询的数据1:" + udao.findById(id));
// 新增两次,会出现主键ID冲突,看是否可以回滚该条数据
udao.insert(user);
System.out.println("查询的数据2:" + udao.findById(id));
udao.insert(user);
} catch (Exception e) {
System.out.println("发生异常,进行手动回滚!");
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return false;
}
3. 指定出发事务回滚的异常类型
在使用事务 @Transactional
的时候,调用了其他的子方法进行了数据库的操作,如果想使其事务生效的话,可以使用rollbackFor注解或者将该子方法的异常抛出由调用的方法进行处理,不过这里需要注意的是,子方法也必须是公共的方法
@Transactional
public boolean test3(User user) {
/*
* 子方法出现异常进行回滚
*/
try {
System.out.println("查询的数据1:" + udao.findById(user.getId()));
deal1(user);
deal2(user);
deal3(user);
} catch (Exception e) {
System.out.println("发生异常,进行手动回滚!");
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return false;
}
public void deal1(User user) throws SQLException {
udao.insert(user);
System.out.println("查询的数据2:" + udao.findById(user.getId()));
}
public void deal2(User user) throws SQLException{
if(user.getAge()<20){
//SQL异常
udao.insert(user);
}else{
user.setAge(21);
udao.update(user);
System.out.println("查询的数据3:" + udao.findById(user.getId()));
}
}
@Transactional(rollbackFor = SQLException.class)
public void deal3(User user) {
if(user.getAge()>20){
//SQL异常
udao.insert(user);
}
}
4. 自定义事务
如果不想使用事务 @Transactional 注解,想自己进行事务控制(编程事务管理),控制某一段的代码事务生效,但是又不想去编写那么多的代码,那么可以使用SpringBoot中的DataSourceTransactionManager和TransactionDefinition这两个类来结合使用,能够达到手动控制事务的提交回滚。不过在进行使用的时候,需要注意在回滚的时候,要确保开启了事务但是未提交,如果未开启或已提交的时候进行回滚是会在catch里面发生异常的!
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
public boolean test4(User user) {
/*
* 手动进行事务控制
*/
//事务状态
TransactionStatus transactionStatus=null;
//判断是否提交事务的标志
boolean isCommit = false;
try {
//创建一个新的事务
transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
System.out.println("查询的数据1:" + udao.findById(user.getId()));
// 进行新增/修改
udao.insert(user);
System.out.println("查询的数据2:" + udao.findById(user.getId()));
if(user.getAge()<20) {
user.setAge(user.getAge()+2);
udao.update(user);
System.out.println("查询的数据3:" + udao.findById(user.getId()));
}else {
throw new Exception("模拟一个异常!");
}
//手动提交
dataSourceTransactionManager.commit(transactionStatus);
isCommit= true;
System.out.println("手动提交事务成功!");
throw new Exception("模拟第二个异常!");
} catch (Exception e) {
//如果未提交就进行回滚
if(!isCommit){
System.out.println("发生异常,进行手动回滚!");
//手动回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
}
e.printStackTrace();
}
return false;
}
重要代码分析
这是一个在Java程序中使用Spring框架的DataSourceTransactionManager进行数据库事务控制的代码片段。具体而言,可以将该代码片段分为以下几个部分:文章来源:https://uudwc.com/A/AA1m4
- dataSourceTransactionManager:这是一个Spring提供的事务管理器,用于管理事务的创建、提交和回滚等操作。通常情况下,该对象需要在Spring的配置文件中进行配置。
- getTransaction(transactionDefinition):该方法用于创建并返回一个新事务。在Spring框架中,事务分为两种类型:PlatformTransactionManager和TransactionDefinition。其中,PlatformTransactionManager是一个接口,定义了创建、提交和回滚事务的方法,常用的实现类有DataSourceTransactionManager、HibernateTransactionManager等;TransactionDefinition则是一个接口,定义了事务的隔离级别、超时时间、只读属性等事务属性。
- transactionStatus:该对象代表了一个事务的状态,包括是否新建、是否已经提交、是否已经回滚等。在Spring框架中,调用transactionManager的getTransaction方法时会返回一个TransactionStatus对象,用于表示当前事务的状态信息。
综上所述,该代码片段的作用是使用Spring框架的DataSourceTransactionManager创建并返回一个新的事务,并将该事务的状态信息保存到TransactionStatus对象中,以备在后续的代码中使用。文章来源地址https://uudwc.com/A/AA1m4
transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
5. 设置断点进行回滚
Object savePoint =null;
try{
//设置回滚点
savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
}catch(Exception e){
//出现异常回滚到savePoint。
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
}