Spring事务的处理流程、传播属性、及部分释疑
目录
文章目录
- 一、spring事务利用AOP的拦截和处理流程
- 必须记住的两个要点
- 二、事务传播属性
- 三、为什么很多Exception异常必须配置在rollback-for中才有用
- 四、事务的传播性在同一个类中方法互调时为什么会失效?
spring或是AOP参考: spring概要
一、spring事务利用AOP的拦截和处理流程
如果知道了事务的处理流程,去理解事务传播属性导致的回滚就是分分钟的事儿了。
想要知道事务的拦截处理流程,只需要分析TransactionAspectSupport类的部分源码即可
// 事务拦截处理方法
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);final String joinpointIdentification = this.methodIdentification(method, targetClass);// 以下代码只是部分代码,为了方便看只截取了能说明问题的部分// 1.获取事务TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 2.处理真正调用的方法retVal = invocation.proceedWithInvocation();} catch (Throwable var15) {// 3.有异常回滚:this.completeTransactionAfterThrowing(txInfo, var15);throw var15;} finally {this.cleanupTransactionInfo(txInfo);}// 4.处理方法完成,提交事务this.commitTransactionAfterReturning(txInfo);return retVal;}
从上面注释可以看到,任何一个被事务拦截的方法,都是先在真正调用该方法之前获取了事务,执行完该方法后再决定是事务回滚或提交。
我们还可以看到,调用处理真正要执行的方法是被try catch的,而catch块里才会有事务回滚的代码。所以,如果某个事务方法A内部有异常没有抛出来(被自己的try cathch块捕获了),而不能被这里的try catch块捕获到,那么这个方法A就不会被回滚。
不会回滚的例子:
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)@Overridepublic void saveTx() {try {Log log = new Log();log.setTitle("saveTx");log.setBeginDate(new Date());logService.save(log);int i=0;int j=2/i;}catch (Exception e){e.printStackTrace();}}
将会回滚的例子:
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)@Overridepublic void saveTx() { Log log = new Log();log.setTitle("saveTx");log.setBeginDate(new Date());logService.save(log);int i=0;int j=2/i; }
在我们写代码的时候,有时候会考虑某个方法是否应该被回滚,那么我们就得注意方法里的异常是否该被try catch是非常重要的。
还有一点,如果一个事务方法调用了一个非事务方法,那么非事务方法就要看作是事务方法的一部分,它们共用的一个数据库连接。
必须记住的两个要点
- 事务拦截处理方法中,只要执行到catch块里的事务回滚代码才会使事务回滚【理解什么时候回滚的关键】。
- 同一个事务里,任何地方导致执行了事务拦截方法里的回滚代码,就会导致整个事务回滚【共用一个DB Conncetion】。
二、事务传播属性
表格中只要说到加入父事务后,就为同一个事务,他们会共用一个Connection
传播属性 | 说明 |
required | 如果当前存在事务则加入该事务,如果没有则新建一个事务 |
supports | 如果当前存在事务则加入该事务,如果没有则以非事务的方式运行。 |
mandatory | 强制加入当前的事务。如果当前没有事务,就抛出异常。 |
requires_new | 新建事务。如果当前存在事务,把当前事务挂起。 |
not_supported | 不支持事务。如果当前存在事务,就把当前事务挂起,然后以非事务方式执行。 |
never | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
nested | 如果当前存在事务,则嵌套执行(相当于指定了回滚点SavePoint)。如果当前没有事务,则新建事务。 |
**nested** 时,如果嵌套事务发生异常回滚,如果父事务捕获了异常,则父事务是不会回滚的,因为它只会回滚到指定的回滚点。
三、为什么很多Exception异常必须配置在rollback-for中才有用
经历过的都知道,Exception异常如果不在rollback-for属性当中指定,即使出现了Exception异常也不会发生事务回滚。
这是因为spring事务处理时,只对RuntimeException和Error异常进行了处理,而Exception没有在其中。
我们看看TransactionAspectSupport类回滚方法里的代码:下面代码只看注释就行了
protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.hasTransaction()) {if (this.logger.isTraceEnabled()) {this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);}// 判断是否是要回滚的异常:rollbackOn方法代码在后面if (txInfo.transactionAttribute.rollbackOn(ex)) {try {// 回滚txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());} catch (TransactionSystemException var7) {this.logger.error("Application exception overridden by rollback exception", ex);var7.initApplicationException(ex);throw var7;} catch (RuntimeException var8) {this.logger.error("Application exception overridden by rollback exception", ex);throw var8;} catch (Error var9) {this.logger.error("Application exception overridden by rollback error", ex);throw var9;}// 不在指定异常范围内} else {try {// 事务提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());} catch (TransactionSystemException var4) {this.logger.error("Application exception overridden by commit exception", ex);var4.initApplicationException(ex);throw var4;} catch (RuntimeException var5) {this.logger.error("Application exception overridden by commit exception", ex);throw var5;} catch (Error var6) {this.logger.error("Application exception overridden by commit error", ex);throw var6;}}}}
异常判断方法:
// RuleBasedTransactionAttribute类的方法
public boolean rollbackOn(Throwable ex) {if (logger.isTraceEnabled()) {logger.trace("Applying rules to determine whether transaction should rollback on " + ex);}RollbackRuleAttribute winner = null;int deepest = 2147483647;if (this.rollbackRules != null) {Iterator var4 = this.rollbackRules.iterator();// 遍历rollback规则while(var4.hasNext()) {RollbackRuleAttribute rule = (RollbackRuleAttribute)var4.next();// 查找当前异常是否在规则里面int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}if (logger.isTraceEnabled()) {logger.trace("Winning rollback rule is: " + winner);}// 该异常没在rollback规则里,则再去判断是否是RuntimeException或Errorif (winner == null) {logger.trace("No relevant rollback rule found: applying default rules");// 这里调用的方法参考return super.rollbackOn(ex);} else {// rollback规则里有此异常就返true,即要回滚return !(winner instanceof NoRollbackRuleAttribute);}}
代码片段3:
// 如果是RuntimeException或Error异常的子类就返回truepublic boolean rollbackOn(Throwable ex) {// 只指定了这两个异常,没有Exception异常return ex instanceof RuntimeException || ex instanceof Error;}
rollback-for属性只针对Error和RuntimeException
我在网上找了一个特别合适的类图来说明:
四、事务的传播性在同一个类中方法互调时为什么会失效?
我们知道,spring的事务都是通过AOP动态代理实现的。如果想要任何一个方法实现事务代理,就必须通过事务代理类去调用,而内部方法调用与事务代理类一点我关系都木有,所以不会生效。下面我举个例子
要代理的接口类:
public interface Subject {void methodOne();void methodTwo();
}
要代理的真实类:
public class RealSubject implements Subject {@Overridepublic void methodOne() {System.out.println("one");this.menthoTwo();}@Overridepublic void methodTwo() {System.out.println("two");}
}
代理类要调用的事务处理器(代理类就是通过它对方法实现的额外处理):
public class MyInvocationHandler implements InvocationHandler {private Object obj;public MyInvocationHandler(){}public MyInvocationHandler(Object obj){this.obj = obj;}// 调用方法前的额外处理:记录方法调用开始时间public void startRecordRequestTime(){System.out.println("方法调用开始时间:"+System.currentTimeMillis());}// 调用方法前的额外处理:模拟处理事务的传播属性public void processTransaction(){System.out.println("事务处理..........");}// 调用方法后的额外处理:记录方法调用结束时间public void endRecordRequestTime(){System.out.println("方法调用结束时间:"+System.currentTimeMillis());}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {this.startRecordRequestTime();// 调用处理事务传播属性方法this.processTransaction();// 通过反射执行真实的方法method.invoke(obj,args);this.endRecordRequestTime();return null;}
}
使用代理访问:
public class Test { public static void main(String[] args) {Subject sub = new RealSubject();MyInvocationHandler handler = new MyInvocationHandler(sub);// 利用反射动态生成的代理类Subject proxy=(Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},handler);// 通过代理类访问方法proxy.methodOne();}
}//运行结果为:
方法调用开始时间:1531564567403
事务处理..........
one
two
方法调用结束时间:1531564567403
.
好了,从执行了proxy.methodOne();
这句代码后的结果能看到,当代理类调用了methodOne()
后,methodTwo()
是在内部调用的,与代理类一点儿关系也没有,所以methodOne
调用内部的methodTwo
时,是不会走事务处理代码的,所以事务传播属性也就会失效。
上面动态生成的代理类大概如下,有兴趣的可以了解下,重点是下面的重写方法调用的MyInvocationHandler
类的invoke
方法:
public class $Proxy0 extends Proxy implements Subject {private static Method m0;private static Method m1;private static Method m2;private static Method m3;private static Method m4;static {try {m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]);// 方法1m3 = Class.forName("***.RealSubject").getMethod("methodOne",new Class[0]);// 方法2m4 = Class.forName("***.RealSubject").getMethod("methodTwo",new Class[0]);} catch (NoSuchMethodException nosuchmethodexception) {throw new NoSuchMethodError(nosuchmethodexception.getMessage());} catch (ClassNotFoundException classnotfoundexception) {throw new NoClassDefFoundError(classnotfoundexception.getMessage());}}public $Proxy0(InvocationHandler invocationhandler) {super(invocationhandler);}@Overridepublic final int hashCode() {try {return ((Integer) super.h.invoke(this, m0, null)).intValue();} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic final String toString() {try {return (String) super.h.invoke(this, m2, null);} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}// 重写Subject接口方法一@Overridepublic final void methodOne() {try {// 调用事务增强处理方法super.h.invoke(this,m3,null);} catch (Throwable throwable) {throwable.printStackTrace();}}// 重写Subject接口方法二@Overridepublic final void methodTwo() {try {// 调用事务增强处理方法super.h.invoke(this,m4,null);} catch (Throwable throwable) {throwable.printStackTrace();}}}
Spring事务的处理流程、传播属性、及部分释疑相关推荐
- spring 事务隔离级别和传播行为_Java工程师面试1000题146-Spring数据库事务传播属性和隔离级别...
146.简介一下Spring支持的数据库事务传播属性和隔离级别 介绍Spring所支持的事务和传播属性之前,我们先了解一下SpringBean的作用域,与此题无关,仅做一下简单记录. 在Spring中 ...
- spring 事务隔离级别和传播行为_Spring事务的传播行为案例分析
网上关于Spring事务传播性以及隔离型的文章漫天盖地,还有不负责任的直接复制名词意思,文章虽然很多却是看的云里雾里,我们今天将给出案例分别和大家一起学习. 1.spring给出经常面试的考点Spri ...
- spring事务隔离级别、传播行为以及spring+mybatis+atomikos实现分布式事务管理
1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...
- 继续卷!面试又问Spring 事务有几种传播行为和隔离级别?
怕什么真理无穷 进一步有近一步的欢喜 面试又被问到了事务,来吧,要么卷起来,要么躺平.卷不动躺平会不会导致数据不一致? 事务概念 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序 ...
- spring 事务隔离级别和传播行为_Spring事务传播实战
事务传播实战 事务具有四个特性 --ACID.其中 A 代表原子性,意思是一个事务要么成功(将结果写入数据库),要么失败(不对数据库有任何影响).这种方式在一个事务单打独斗的时候是一个非常好的做法,但 ...
- spring 事务隔离级别和传播行为_Spring事务传播性与隔离性实战
一.事务传播性 1.1 什么是事务的传播性 事务的传播性一般在事务嵌套时候使用,比如在事务A里面调用了另外一个使用事务的方法,那么这俩个事务是各自作为独立的事务执行提交,还是内层的事务合并到外层的事务 ...
- Spring事务传播属性有那么难吗?看这一篇就够了
点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:不学无数的程序员 juejin.im/post/5da6eee2f265da5bb9 ...
- Spring 事务传播属性有那么难吗?看这一篇就够了!
尊重原创,原文链接 学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性. 传播属性 传播属性定义的是当一个 ...
- 京东电话面:说说你对Spring事务传播属性的理解?
点击上方蓝色"java大数据修炼之道", 选择"设为星标" 每晚九点: 技术干货 ???? 必定奉上哈喽,各位新来的小伙伴们,大家好!由于公众号做了改版,为了保 ...
最新文章
- 介绍一款开源的类Excel电子表格软件
- 使用Linux命令来发送信息
- 在WebRTC上实现ML Kit笑容检测
- linux以预置密码进行验证拒绝访问,Linux重置MySQL密码
- 数通手稿留档——Multicast
- 牛客 - 17968 - xor序列 - 线性基
- PHP中的Traits用法详解
- 张晓霞oracle,《Oracle应用开发》实验指导书 - 图文
- 转:为 setuptools 开路搭桥
- vmware funsion 共享网络模式下PPTP拨号问题
- 使Fiddler4抓包微信小程序
- 梅宏:不容错过的大数据时代_我们错过了整个网络支付领域:如何为创作者修复网络...
- GDAL自带的 rpc纠正和金字塔文件生成方法
- hive mapreduce reducer 调优
- java怎么设置表格分页显示_javaweb--layui表格分页
- 方便地边看便翻译原版pdf文章(wps)
- F005MyBatis学习笔记-MyBatis的多表关联查询
- 数据挖掘——机器学习
- gravity和layout_gravity的区别(有时使用layout_gravity=center时失效的原因)
- JavaScript中的函数中arguments、参数、默认值和表达式以及箭头函数
热门文章
- MySQL的索引及优化方案
- recv函数返回值说明
- 【web安全】Spring Data Commons 1.13.10 SpEL漏洞分析
- 【Laravel】Fatal error: Declaration of Illuminate\Container\Container::get($id) must be compatible
- 11、MySQL常见错误代码一览表
- 利用堆排序查找数组中第K小的元素方法
- 51nod 1256 乘法逆元(扩展欧几里得)
- Linux文件系统目录结构
- C语言常用的字符串函数
- Java继承Thread类创建多线程