SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)(事务失效)...
参考:https://blog.csdn.net/zzhongcy/article/details/102893309
概念
事务定义
事务特点
1.原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
3.隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰
事务实现机制
Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。
- 编程式事务管理: 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
- 声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
- 声明式事务管理不需要入侵代码,更快捷而且简单,推荐使用。
一种是在配置文件(xml)中做相关的事务规则声明
另一种是基于 @Transactional 注解的方式。注释配置是目前流行的使用方式,推荐使用。
在应用系统调用声明了 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。
开启事务
Spirng Boot 默认开启事务,无需做任何事情,直接使用@Transactional即可
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="pooledDataSource"/>
</bean>
<aop:config><aop:pointcut expression="execution(* cn.yuanyu.crud.service..*(..))" id="txPoint"/><aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*"/><tx:method name="get*" read-only="true"/></tx:attributes>
</tx:advice>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="pooledDataSource"/>
</bean>
<aop:config><aop:pointcut expression="execution(* cn.yuanyu.crud.service..*(..))" id="txPoint"/><aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*"/><tx:method name="get*" read-only="true"/></tx:attributes>
</tx:advice>
<!--
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
方式3:Spring中 纯注解 配置事务,使用**@EnableTransactionManagement** 注解也可以启用事务管理功能
@Configuration //声明配置类
@MapperScan("cn.yuanyu.tx.mapper")
@EnableTransactionManagement // 开启事务注解,等同于配置文件<tx:annotation-driven/>
public class MybatisPlusConfiguration {
注解@Transactional的使用
注解@Transactional常用配置
参 数 名 称 | 功 能 描 述 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 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表示永不超时 事物超时设置:@Transactional(timeout=30) ,设置为30秒 |
Propagation的属性(事务的传播行为)
例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
Propagation属性 | 含义 |
---|---|
REQUIRED | 默认值 在有transaction状态下执行;如当前没有transaction,则创建新的transaction; |
SUPPORTS | 如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行; |
MANDATORY | 必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException; |
REQUIRES_NEW | 创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起; |
NOT_SUPPORTED | 在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起; |
NEVER | 在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。 |
事务5种隔离级别
例如:@Transactional(isolation = Isolation.READ_COMMITTED)
隔离级别 | 含义 |
---|---|
DEFAULT | 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别另外四个与JDBC的隔离级别相对应; |
READ_UNCOMMITTED | 最低的隔离级别。事实上我们不应该称其为隔离级别,因为在事务完成前,其他事务可以看到该事务所修改的数据。而在其他事务提交前,该事务也可以看到其他事务所做的修改。可能导致脏,幻,不可重复读 |
READ_COMMITTED | 大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。遗憾的是,在该事务提交后,你就可以查看其他事务插入或更新的数据。这意味着在事务的不同点上,如果其他事务修改了数据,你就会看到不同的数据。可防止脏读,但幻读和不可重复读仍可以发生。 |
REPEATABLE_READ | 比ISOLATION_READ_COMMITTED更严格,该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的数据集,即使其他事务修改了所查询的数据。然而如果其他事务插入了新数据,你就可以查询到该新插入的数据。可防止脏读,不可重复读,但幻读仍可能发生。 |
SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行。避免所有不安全读取。 |
使用注意事项(防止事务失效)
在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
@Transactional 注解应该只被应用在 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 该注解,它也不会报错(IDEA会有提示), 但事务并没有生效。
被外部调用的公共方法A有两个进行了数据操作的子方法B和子方法C的事务注解说明:
被外部调用的公共方法A声明事务@Transactional,无论子方法B和C是不是本类的方法,无论子方法B和C是否声明事务,事务均由公共方法A控制
被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是其他类的方法且各自声明事务:事务由子方法B和C各自控制
被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是本类的方法,则即使子方法B和C各自声明事务,事务也不会生效,并且会报错(没有可用的transactional)
被外部调用的公共方法A声明事务@Transactional,子方法运行异常,但运行异常被子方法自己
try-catch
处理了,则事务回滚是不会生效的!如果想要事务回滚生效,需要将子方法的事务控制交给调用的方法来处理:
- 方案1:子方法中不用
try-catch
处理运行异常 - 方案2:子方法的catch里面将运行异常抛出【throw new RuntimeException();】
- 方案1:子方法中不用
默认情况下,Spring会对unchecked异常进行事务回滚,也就是默认对 RuntimeException() 异常或是其子类进行事务回滚。
如果是checked异常则不回滚,例如空指针异常、算数异常等会被回滚;文件读写、网络问题Spring就没法回滚。
若想对所有异常(包括自定义异常)都起作用,注解上面需配置异常类型:@Transactional(rollbackFor = Exception.class
数据库要支持事务,如果是mysql,要使用innodb引擎,myisam不支持事务
事务@Transactional由spring控制时,它会在抛出异常的时候进行回滚。如果自己使用try-catch捕获处理了,是不生效的。如果想事务生效可以进行手动回滚或者在catch里面将异常抛出【throw new RuntimeException();】
方案一:手动抛出运行时异常(缺陷是不能在catch代码块自定义返回值)
try{.... }catch(Exception e){logger.error("",e);throw new RuntimeException;}
方案二:手动进行回滚【 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 】
try{...}catch(Exception e){log.error("fail",e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return false;}
@Transactional可以放在Controller下面直接起作用,看到网上好多同学说要放到@Component下面或者@Service下面,经过试验,可以不用放在这两个下面也起作用。
@Transactional引入包问题,它有两个包:
import javax.transaction.Transactional; // 和 import org.springframework.transaction.annotation.Transactional; // 推荐
这两个都可以用,对比了一下他们两个的方法和属性,发现后面的比前面的强大。建议使用后面的。
使用场景
自动回滚
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder() throws Exception { success(); //假如exception这个操作数据库的方法会抛出异常,方法success()对数据库的操作会回滚。 exception(); return ApiReturnUtil.success();
}
手动回滚
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){ success(); try { exception(); } catch (Exception e) { e.printStackTrace(); //手工回滚异常TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return ApiReturnUtil.error();} return ApiReturnUtil.success();
}
回滚部分异常
使用【Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 】设置回滚点。
使用【TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);】回滚到savePoint。
@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){ success(); //只回滚以下异常,Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();try { exception(); } catch (Exception e) { e.printStackTrace(); //手工回滚异常TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);return ApiReturnUtil.error();} return ApiReturnUtil.success();
}
手动创建、提交、回滚事务
@Autowired
priDataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;// 手动创建事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 手动提交事务
dataSourceTransactionManager.commit(transactionStatus);// 手动回滚事务。(最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交)
dataSourceTransactionManager.rollback(transactionStatus);
事务失效不回滚的问题
异常被捕获导致事务失效
在spring boot 中,使用事务非常简单,直接在方法上面加入@Transactional 就可以实现,以下是我的做法
@GetMapping("delete")
@ResponseBody
@Transactional
public void delete(@RequestParam("id") int id) { try { //delete countrythis.repository.delete(id); if(id == 1){ throw Exception("测试事务");} //delete citythis.repository.deleteByCountryId(id);}catch (Exception e){logger.error("delete false:" + e.getMessage()); return new MessageBean(101,"delete false");}
}
发现事务不回滚,即 this.repository.delete(id);成功把数据删除了。
默认spring事务只在发生未被捕获的 RuntimeException 时才回滚。
换句话说在service的方法中不使用 try catch 或者在 catch 中最后加上throw new RuntimeExcetpion()抛出运行异常,这样程序异常时才能被aop捕获进而回滚。
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加
throw new RuntimeException();
语句,以便让aop捕获异常再去回滚,并且在service的上层要继续捕获这个异常。方案2:在service层方法的catch语句中进行手动回滚,这样上层就无需去处理异常。
@GetMapping("delete") @ResponseBody @Transactional public Object delete(@RequestParam("id") int id){ if (id < 1){return new MessageBean(101,"parameter wrong: id = " + id) ; } try { //delete countrythis.countryRepository.delete(id);//delete citythis.cityRepository.deleteByCountryId(id);return new MessageBean(200,"delete success");}catch (Exception e){logger.error("delete false:" + e.getMessage());// 手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return new MessageBean(101,"delete false");} }
自调用导致事务失效
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,否则会造成自调用问题。
@Service
public class OrderService {private void insert() {insertOrder();}@Transactionalpublic void insertOrder() {//insert log info//insertOrder//updateAccount}
}// insertOrder() 尽管有@Transactional 注解,但它被内部方法 insert()调用,事务被忽略,出现异常事务不会发生回滚,并且会报错类似于:
// org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope(翻译:没有Transaction无法回滚事务。自调用导致@Transactional 失效。)
spring里事务是用注解配置的,当一个方法没有接口,单单只是一个内部方法时,事务的注解是不起作用的,需要回滚时就会报错。
出现这个问题的根本原因在于AOP的实现原理。由于@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,换句话说,自调用时不存在代理对象的调用,这时不会产生我们注解@Transactional 配置的参数,自然无效了。
虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。
解决方案:
1、可以在类服务上加事务(或者最外层的接口方法上加事务):
@Transactional
public class MyTransactional {//@Transactional 服务上加了@Transactional后 这里可以不加public UserEntity login(UserEntity user) {this.test();return user;}public void test(){userDao.update(6);if(userDao.update(6)){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}
}
2、使用AspectJ 取代 Spring AOP 代理
上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,可以使用 AspectJ 取代 Spring AOP 代理。
需要将下面的 AspectJ 信息添加到 xml 配置信息中。
AspectJ 的 xml 配置信息
<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" />
</bean>
</bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf"><property name="transactionManager" ref="transactionManager" />
</bean>
同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。
AspectJ 的 pom 配置信息
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.2.RELEASE</version></dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version>
</dependency><plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration><showWeaveInfo>true</showWeaveInfo><aspectLibraries><aspectLibrary><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></aspectLibrary></aspectLibraries>
</configuration><executions>
<execution><goals><goal>compile</goal><goal>test-compile</goal></goals>
</execution>
</executions>
</plugin>
其他
事务提交方式
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
事务并发会产生的问题
术语 | 含义 |
---|---|
脏读 | A事务读取到了B事务还未提交的数据,如果B未提交的事务回滚了,那么A事务读取的数据就是无效的,这就是数据脏读 |
不可重复读 | 在同一个事务中,多次读取同一数据返回的结果不一致,这是由于读取事务在进行操作的过程中,如果出现更新事务,它必须等待更新事务执行成功提交完成后才能继续读取数据,这就导致读取事务在前后读取的数据不一致的状况出现 |
幻读 | A事务读取了几行记录后,B事务插入了新数据,并且提交了插入操作,在后续操作中A事务就会多出几行原本不存在的数据,就像A事务出现幻觉,这就是幻读 |
第一类丢失更新
在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了。
脏读
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读
第二类丢失更新
有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
幻读
不可重复读和幻读的区别
来源:https://blog.csdn.net/footless_bird/article/details/119530607
SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)(事务失效)...相关推荐
- SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)
参考:https://blog.csdn.net/zzhongcy/article/details/102893309 概念 事务定义 事务,就是一组操作数据库的动作集合.事务是现代数据库理论中的核心 ...
- SpringBoot异常处理ErrorController详解
文章目录 一.背景 二.SpringBoot的默认异常处理BasicErrorController 三.自定义错误异常 写在前面: 我是「境里婆娑」.我还是从前那个少年,没有一丝丝改变,时间只不过是考 ...
- Centos网络配置方法详解(自动获取、手动设置)
一.配置文件详解 在RHEL或者CentOS等Redhat系的Linux系统里,跟网络有关的主要设置文件如下: /etc/host.conf 配置域名服务客户端的控制文件 /etc/h ...
- SpringBoot 整合EasyExcel详解(一)-高性能Excel方案
SpringBoot 整合EasyExcel详解(二)-写Excel SpringBoot 整合EasyExcel详解(三)-填充Excel-官方原版 一.概述 Java解析.生成Excel比较有名的 ...
- SpringBoot的配置详解application
SpringBoot的配置文件application有两种文件格式,两种配置的内容是一致的,只是格式不一致. 1.application.properties 2.application.yml或者a ...
- springboot+jsp中文乱码_【spring 国际化】springMVC、springboot国际化处理详解
在web开发中我们常常会遇到国际化语言处理问题,那么如何来做到国际化呢? 你能get的知识点? 使用springgmvc与thymeleaf进行国际化处理. 使用springgmvc与jsp进行国际化 ...
- creo配置文件config选项详解_5年资深架构师重点聚焦:SpringBoot的配置详解+内嵌Servlet容器
Spring Boot的配置详解 在本节中,我们将重点聚焦在Spring Boot的配置方面. 理解Spring Boot的自动配置 按照"约定大于配置"的原则,Spring Bo ...
- java框架魔乐_16 魔乐科技 SpringBoot框架开发详解
资源内容: 16 魔乐科技 SpringBoot框架开发详解|____springboot开发代码.rar|____第一章:SpringBoot入门 |____2. SpringBo ...
- java异常处理机制详解
java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.
最新文章
- PAT甲级1020 Tree Traversals:[C++题解]树的遍历、由中序序列和后序序列递归建树
- 深度探索C++ 对象模型(1)-三种对象模型的设计
- ubuntu20.04 黑屏 进入不了图形界面 dev/sda1: clean
- 数据结构:静态链表实现树的同构
- JS的typeof力所能及已经力所不及
- HDU 3951 (博弈) Coin Game
- VS2010可反复安装MSDN的方法
- python commands用法_python之commands模块(执行Linux Shell命令)
- SEO工具,站长必备
- 又被腾讯刷屏!果然是印钞机.....酸哭了...
- 计算机无法安装蓝牙驱动,W7系统蓝牙驱动安装失败的原因分析与解决方法
- 英文环境中Wine微信不能显示中文
- jq 判断手机端还是PC端
- wi ndows防火墙,网吧的防火墙怎么关?四种方法关闭WINDOWS防火墙
- c语言贺卡代码大全,C++实现新年贺卡程序
- python阿拉伯数字转换为英文_python实现将英文单词表示的数字转换成阿拉伯数字的方法...
- Cisco 3550交换机IOS备份(真实设备演示)
- 计算机毕业设计ssm校园竞赛管理系统设计与实现hyr9b系统+程序+源码+lw+远程部署
- C#连接数据库自动生成实体类
- 不可随便给一个人说晚安『你知道它的真正意义吗?不想以后后悔就进来看看吧』
热门文章
- 家电行业被迫“大象转身”,破局出路在哪?
- Fundamentals of Computer Graphics 4th目录
- 怎么看服务器cpu温度命令_服务器cpu温度查看
- ATK BLE 01低功耗蓝牙配对调试经验
- 2018年12月计算机二级C语言题库,2016年12月计算机二级《C语言》选择题题库
- 探探速锐得驾考OBD模块内核“技术”功夫到底如何?
- PDH SDH WDM
- 加入昇思MindFlow SIG,助力AI流体仿真发展
- 网友:真实用,把照片变视频,每天收入100元,比上班强10倍
- 华为AC控制器新增SSID