springboot 事务手动回滚_Spring Boot中的事务是如何实现的
1. 概述
一直在用SpringBoot中的@Transactional
来做事务管理,但是很少想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional
是如何实现事务的,最后我们结合源码的理解,自己动手写一个类似的注解来实现事务管理,帮助我们加深理解。
阅读说明:本文假设你具备Java基础,同时对事务有基本的了解和使用。
2. 事务的相关知识
开始看源码之前,我们先回顾下事务的相关知识。
2.1 事务的隔离级别
事务为什么需要隔离级别呢?这是因为在并发事务情况下,如果没有隔离级别会导致如下问题:
脏读(Dirty Read) :当A事务对数据进行修改,但是这种修改还没有提交到数据库中,B事务同时在访问这个数据,由于没有隔离,B获取的数据有可能被A事务回滚,这就导致了数据不一致的问题。
丢失修改(Lost To Modify): 当A事务访问数据100,并且修改为100-1=99,同时B事务读取数据也是100,修改数据100-1=99,最终两个事务的修改结果为99,但是实际是98。事务A修改的数据被丢失了。
不可重复读(Unrepeatable Read):指A事务在读取数据X=100的时候,B事务把数据X=100修改为X=200,这个时候A事务第二次读取数据X的时候,发现X=200了,导致了在整个A事务期间,两次读取数据X不一致了,这就是不可重复读。
幻读(Phantom Read):幻读和不可重复读类似。幻读表现在,当A事务读取表数据时候,只有3条数据,这个时候B事务插入了2条数据,当A事务再次读取的时候,发现有5条记录了,平白无故多了2条记录,就像幻觉一样。
不可重复读 VS 幻读
不可重复读的重点是修改 : 同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了,重点在更新操作。幻读的重点在于新增或者删除:同样的条件 , 第 1 次和第 2 次读出来的记录数不一样,重点在增删操作。
所以,为了避免上述的问题,事务中就有了隔离级别的概念,在Spring中定义了五种表示隔离级别的常量:
2.2 Spring中事务的传播机制
为什么Spring中要搞一套事务的传播机制呢?这是Spring给我们提供的事务增强工具,主要是解决方法之间调用,事务如何处理的问题。比如有方法A、方法B和方法C,在A中调用了方法B和方法C。伪代码如下:
MethodA{ MethodB; MethodC;}MethodB{}MethodC{}
假设三个方法中都开启了自己的事务,那么他们之间是什么关系呢?MethodA
的回滚会影响MethodB
和MethodC
吗?Spring中的事务传播机制就是解决这个问题的。
Spring中定义了七种事务传播行为:
3. 如何实现异常回滚的
回顾完了事务的相关知识,接下来我们正式来研究下Spring Boot中如何通过@Transactional
来管理事务的,我们重点看看它是如何实现回滚的。
在Spring中TransactionInterceptor
和PlatformTransactionManager
这两个类是整个事务模块的核心,TransactionInterceptor
负责拦截方法执行,进行判断是否需要提交或者回滚事务。PlatformTransactionManager
是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。我们重点研究下这两个类的源码。
TransactionInterceptor
类中的代码有很多,我简化一下逻辑,方便说明:
//以下代码省略部分内容 public Object invoke(MethodInvocation invocation) throws Throwable { //获取事务调用的目标方法 Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); //执行带事务调用 return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
invokeWithinTransaction 简化逻辑如下:
//TransactionAspectSupport.class //省略了部分代码 protected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass, final InvocationCallback invocation) throws Throwable { Object retVal; try { //调用真正的方法体 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 如果出现异常,执行事务异常处理 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { //最后做一下清理工作,主要是缓存和状态等 cleanupTransactionInfo(txInfo); } //如果没有异常,直接提交事务。 commitTransactionAfterReturning(txInfo); return retVal; }
事务出现异常回滚的逻辑completeTransactionAfterThrowing
如下:
//省略部分代码protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { //判断是否需要回滚,判断的逻辑就是看有没有声明事务属性,同时判断是不是在目前的这个异常中执行回滚。 if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { //执行回滚 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } else { //否则不需要回滚,直接提交即可。 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } }
上面的代码已经把Spring的事务的基本原理说清楚了,如何进行判断执行事务,如何回滚。下面到了真正执行回滚逻辑的代码中PlatformTransactionManager
接口的子类,我们以JDBC的事务为例,DataSourceTransactionManager
就是jdbc的事务管理类。跟踪上面的代码rollback(txInfo.getTransactionStatus())
可以发现最终执行的代码如下:
@Override protected void doRollback(DefaultTransactionStatus status) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); } try { //调用jdbc的 rollback进行回滚事务。 con.rollback(); } catch (SQLException ex) { throw new TransactionSystemException("Could not roll back JDBC transaction", ex); } }
3.1 小结
这里小结下Spring 中事务的实现思路,Spring 主要依靠 TransactionInterceptor
来拦截执行方法体,判断是否开启事务,然后执行事务方法体,方法体中catch
住异常,接着判断是否需要回滚,如果需要回滚就委托真正的TransactionManager
比如JDBC中的DataSourceTransactionManager
来执行回滚逻辑。提交事务也是同样的道理。
这里用个流程图展示下思路:
4. 手写一个注解实现事务回滚
我们弄清楚了Spring的事务执行流程,那我们可以模仿着自己写一个注解,实现遇到指定异常就回滚的功能。这里持久层就以最简单的JDBC为例。我们先梳理下需求,首先注解我们可以基于Spring 的AOP来实现,接着既然是JDBC,那么我们需要一个类来帮我们管理连接,用来判断异常是否回滚或者提交。梳理完就开干吧。
4.1 首先加入依赖
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-jdbcartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-aopartifactId> dependency>
4.2 新增一个注解
/** * @description: * @author: luozhou * @create: 2020-03-29 17:05 **/@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface MyTransaction { //指定异常回滚 Class extends Throwable>[] rollbackFor() default {};}
4.3 新增连接管理器
该类帮助我们管理连接,该类的核心功能是把取出的连接对象绑定到线程上,方便在AOP处理中取出,进行提交或者回滚操作。
/** * @description: * @author: luozhou * @create: 2020-03-29 21:14 **/@Componentpublic class DataSourceConnectHolder { @Autowired DataSource dataSource; /** * 线程绑定对象 */ ThreadLocal resources = new NamedThreadLocal<>("Transactional resources"); public Connection getConnection() { Connection con = resources.get(); if (con != null) { return con; } try { con = dataSource.getConnection(); //为了体现事务,全部设置为手动提交事务 con.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } resources.set(con); return con; } public void cleanHolder() { Connection con = resources.get(); if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } resources.remove(); }}
4.4 新增一个切面
这部分是事务处理的核心,先获取注解上的异常类,然后捕获住执行的异常,判断异常是不是注解上的异常或者其子类,如果是就回滚,否则就提交。
/** * @description: * @author: luozhou * @create: 2020-03-29 17:08 **/@Aspect@Componentpublic class MyTransactionAopHandler { @Autowired DataSourceConnectHolder connectHolder; Class extends Throwable>[] es; //拦截所有MyTransaction注解的方法 @org.aspectj.lang.annotation.Pointcut("@annotation(luozhou.top.annotion.MyTransaction)") public void Transaction() { } @Around("Transaction()") public Object TransactionProceed(ProceedingJoinPoint proceed) throws Throwable { Object result = null; Signature signature = proceed.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method == null) { return result; } MyTransaction transaction = method.getAnnotation(MyTransaction.class); if (transaction != null) { es = transaction.rollbackFor(); } try { result = proceed.proceed(); } catch (Throwable throwable) { //异常处理 completeTransactionAfterThrowing(throwable); throw throwable; } //直接提交 doCommit(); return result; } /** * 执行回滚,最后关闭连接和清理线程绑定 */ private void doRollBack() { try { connectHolder.getConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } finally { connectHolder.cleanHolder(); } } /** *执行提交,最后关闭连接和清理线程绑定 */ private void doCommit() { try { connectHolder.getConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } finally { connectHolder.cleanHolder(); } } /** *异常处理,捕获的异常是目标异常或者其子类,就进行回滚,否则就提交事务。 */ private void completeTransactionAfterThrowing(Throwable throwable) { if (es != null && es.length > 0) { for (Class extends Throwable> e : es) { if (e.isAssignableFrom(throwable.getClass())) { doRollBack(); } } } doCommit(); }}
4.5 测试验证
创建一个tb_test表,表结构如下:
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for tb_test-- ----------------------------DROP TABLE IF EXISTS `tb_test`;CREATE TABLE `tb_test` ( `id` int(11) NOT NULL, `email` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;SET FOREIGN_KEY_CHECKS = 1;
4.5.1 编写一个Service
saveTest
方法调用了2个插入语句,同时声明了@MyTransaction
事务注解,遇到NullPointerException
就进行回滚,最后我们执行了除以0操作,会抛出ArithmeticException
。我们用单元测试看看数据是否会回滚。
/** * @description: * @author: luozhou kinglaw1204@gmail.com * @create: 2020-03-29 22:05 **/@Servicepublic class MyTransactionTest implements TestService { @Autowired DataSourceConnectHolder holder; //一个事务中执行两个sql插入 @MyTransaction(rollbackFor = NullPointerException.class) @Override public void saveTest(int id) { saveWitharamters(id, "luozhou@gmail.com"); saveWitharamters(id + 10, "luozhou@gmail.com"); int aa = id / 0; } //执行sql private void saveWitharamters(int id, String email) { String sql = "insert into tb_test values(?,?)"; Connection connection = holder.getConnection(); PreparedStatement stmt = null; try { stmt = connection.prepareStatement(sql); stmt.setInt(1, id); stmt.setString(2, email); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } }}
4.5.2 单元测试
@SpringBootTest@RunWith(SpringRunner.class)class SpringTransactionApplicationTests { @Autowired private TestService service; @Test void contextLoads() throws SQLException { service.saveTest(1); }}
上图代码声明了事务对
NullPointerException异常进行回滚,运行中遇到了ArithmeticException
异常,所以是不会回滚的,我们在右边的数据库中刷新发现数据正常插入成功了,说明并没有回滚。
我们把回滚的异常类改为ArithmeticException
,把原数据清空再执行一次,出现了ArithmeticException
异常,这个时候查看数据库是没有记录新增成功了,这说明事物进行回滚了,表明我们的注解起作用了。
5. 总结
本文最开始回顾了事务的相关知识,并发事务会导致脏读、丢失修改、不可重复读、幻读,为了解决这些问题,数据库中就引入了事务的隔离级别,隔离级别包括:读未提交、读提交、可重复读和串行化。
Spring中增强了事务的概念,为了解决方法A、方法B和方法C之间的事务关系,引入了事务传播机制的概念。
Spring中的@Transactional
注解的事务实现主要通过TransactionInterceptor
拦截器来进行实现的,拦截目标方法,然后判断异常是不是目标异常,如果是目标异常就行进行回滚,否则就进行事务提交。
最后我们自己通过JDBC结合Spring的AOP自己写了个@MyTransactional
的注解,实现了遇到指定异常回滚的功能。
出处:https://juejin.im/post/5e7ef0bae51d4546f16bb3fb
springboot 事务手动回滚_Spring Boot中的事务是如何实现的相关推荐
- spring事务手动回滚
近期改已离职员工的代码,发现很多地方存在事务的逻辑性错误,比如: 在已对数据库进行一次增删改的情况下,在下一步增删改中如果执行失败,他直接就返回一个包含错误code的ResultMap的结果集回去(这 ...
- springboot事务回滚源码_Spring Boot中的事务是如何实现的
1. 概述 一直在用SpringBoot中的@Transactional来做事务管理,但是很少想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional是如何实现事 ...
- springboot 事务_Spring Boot中的事务是如何实现的?懂吗?
一个SpringBoot问题就干趴下了?我却凭着这份PDF文档吊打面试官. 金三银四第一天,啃透这些SpringBoot知识点,还怕干不赢面试官? Spring全家桶笔记:Spring+Spring ...
- springboot 事务手动回滚_来,讲讲Spring事务有哪些坑?
来自公众号:孤独烟 引言 今天,我们接上文<面试官:谈谈你对mysql事务的认识>的内容,来讲spring中和事务有关的考题! 因为事务这块,面试的出现几率很高.而大家工作中CRUD的比较 ...
- java事务中使用try catch 导致事务不回滚的问题
@Transactional注解的触发,只回滚RuntimeException和Error异常,默认不回滚非RuntimeException异常 解决方法: 1.方法前添加注解(基础的 @Trans ...
- sqlsever回滚操作_sqlserver事务与回滚
如果要在Production执行数据改动必须小心,可以使用事务提前验证一下自己写的SQL是不是你期望的.尤其是Update的where 条件有问题的话,跟新的记录就会超出预期的范围.如下面的语句,一着 ...
- JAVA Spring 事务管理事务不回滚问题
Spring事务管理事务不回滚 dao层: @Repository public class UserDaoImpl implements UserDao { @Autowired private J ...
- SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)(事务失效)...
参考:https://blog.csdn.net/zzhongcy/article/details/102893309 概念 事务定义 事务,就是一组操作数据库的动作集合.事务是现代数据库理论中的核心 ...
- SpringBoot中try/catch异常并回滚事务(自动回滚/手动回滚/部分回滚)
在Spring官方文档中说到,当Transaction内发生unchecked exception的时候,会自动rollback,但是当Transaction内发生checked exception时 ...
最新文章
- libusb中的热插拔使用举例
- c语言结构体共用体枚举实例程序,10-C语言结构体-共用体-枚举
- php查询mysql表里的数据_php查询mysql数据表记录实现代码
- 分布式系统Kafka和ES中,JVM内存越大越好吗?
- Centos7防火墙的常用指令
- 10个实用的机器学习建议
- Tensorflow图像调整大小
- em算法 实例 正态分布_人人都能看懂的EM算法推导
- 生活娱乐 长的最奇怪的东西——骡耳犰狳
- chrome支持的java版本下载_安装Chrome Java插件
- 信息 | 美国留学之计算机专业【转】
- 论文翻译:(BMVC 2022)You Only Need 90K Parameters to Adapt Light:a Light Weight Transformer
- CTF逆向-[安洵杯 2019]game-使用deflat对主要混淆脱混淆后常规逻辑判断
- Filter过滤器的作用
- 导出单帧图片以及时间线介绍(PR)
- 英汉对照:32个最富哲理的名言警句
- 怎么恢复360删除的文件?360文件恢复,快速完成
- 博瑞智能云音箱云喇叭API开发定时播报文档(2023-4-5)
- jeesite代码生成id出不来的解决方案
- java sql 基础_Java SQL基础
热门文章
- python3项目-终于找到python3项目实战教程
- python3教程-终于清楚python3详细教程
- python怎么写文件-python头文件怎么写
- python可以从事什么工作-学Python可以找什么工作或者做什么兼职?
- c+和python先学哪个比较好-C和Python我该先学什么?
- python工作-Python自动化运维|Python语言工作岗位待遇如何?
- python中文教程-中谷python中文视频教程(全38集)
- python unicode编码书写方式_python 中文编码 小结 ,json读写,str转换unicode,文字比较...
- 深入探寻seajs的模块化与加载方式
- VUE+WebPack游戏开发:神庙逃亡的游戏设计