目录

文章目录

  • 一、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是非常重要的。

还有一点,如果一个事务方法调用了一个非事务方法,那么非事务方法就要看作是事务方法的一部分,它们共用的一个数据库连接。

必须记住的两个要点

  1. 事务拦截处理方法中,只要执行到catch块里的事务回滚代码才会使事务回滚【理解什么时候回滚的关键】。
  2. 同一个事务里,任何地方导致执行了事务拦截方法里的回滚代码,就会导致整个事务回滚【共用一个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事务的处理流程、传播属性、及部分释疑相关推荐

  1. spring 事务隔离级别和传播行为_Java工程师面试1000题146-Spring数据库事务传播属性和隔离级别...

    146.简介一下Spring支持的数据库事务传播属性和隔离级别 介绍Spring所支持的事务和传播属性之前,我们先了解一下SpringBean的作用域,与此题无关,仅做一下简单记录. 在Spring中 ...

  2. spring 事务隔离级别和传播行为_Spring事务的传播行为案例分析

    网上关于Spring事务传播性以及隔离型的文章漫天盖地,还有不负责任的直接复制名词意思,文章虽然很多却是看的云里雾里,我们今天将给出案例分别和大家一起学习. 1.spring给出经常面试的考点Spri ...

  3. spring事务隔离级别、传播行为以及spring+mybatis+atomikos实现分布式事务管理

    1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...

  4. 继续卷!面试又问Spring 事务有几种传播行为和隔离级别?

    怕什么真理无穷 进一步有近一步的欢喜 面试又被问到了事务,来吧,要么卷起来,要么躺平.卷不动躺平会不会导致数据不一致? 事务概念 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序 ...

  5. spring 事务隔离级别和传播行为_Spring事务传播实战

    事务传播实战 事务具有四个特性 --ACID.其中 A 代表原子性,意思是一个事务要么成功(将结果写入数据库),要么失败(不对数据库有任何影响).这种方式在一个事务单打独斗的时候是一个非常好的做法,但 ...

  6. spring 事务隔离级别和传播行为_Spring事务传播性与隔离性实战

    一.事务传播性 1.1 什么是事务的传播性 事务的传播性一般在事务嵌套时候使用,比如在事务A里面调用了另外一个使用事务的方法,那么这俩个事务是各自作为独立的事务执行提交,还是内层的事务合并到外层的事务 ...

  7. Spring事务传播属性有那么难吗?看这一篇就够了

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:不学无数的程序员 juejin.im/post/5da6eee2f265da5bb9 ...

  8. Spring 事务传播属性有那么难吗?看这一篇就够了!

    尊重原创,原文链接 学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性. 传播属性 传播属性定义的是当一个 ...

  9. 京东电话面:说说你对Spring事务传播属性的理解?

    点击上方蓝色"java大数据修炼之道", 选择"设为星标" 每晚九点: 技术干货 ???? 必定奉上哈喽,各位新来的小伙伴们,大家好!由于公众号做了改版,为了保 ...

最新文章

  1. 介绍一款开源的类Excel电子表格软件
  2. 使用Linux命令来发送信息
  3. 在WebRTC上实现ML Kit笑容检测
  4. linux以预置密码进行验证拒绝访问,Linux重置MySQL密码
  5. 数通手稿留档——Multicast
  6. 牛客 - 17968 - xor序列 - 线性基
  7. PHP中的Traits用法详解
  8. 张晓霞oracle,《Oracle应用开发》实验指导书 - 图文
  9. 转:为 setuptools 开路搭桥
  10. vmware funsion 共享网络模式下PPTP拨号问题
  11. 使Fiddler4抓包微信小程序
  12. 梅宏:不容错过的大数据时代_我们错过了整个网络支付领域:如何为创作者修复网络...
  13. GDAL自带的 rpc纠正和金字塔文件生成方法
  14. hive mapreduce reducer 调优
  15. java怎么设置表格分页显示_javaweb--layui表格分页
  16. 方便地边看便翻译原版pdf文章(wps)
  17. F005MyBatis学习笔记-MyBatis的多表关联查询
  18. 数据挖掘——机器学习
  19. gravity和layout_gravity的区别(有时使用layout_gravity=center时失效的原因)
  20. JavaScript中的函数中arguments、参数、默认值和表达式以及箭头函数

热门文章

  1. MySQL的索引及优化方案
  2. recv函数返回值说明
  3. 【web安全】Spring Data Commons 1.13.10 SpEL漏洞分析
  4. 【Laravel】Fatal error: Declaration of Illuminate\Container\Container::get($id) must be compatible
  5. 11、MySQL常见错误代码一览表
  6. 利用堆排序查找数组中第K小的元素方法
  7. 51nod 1256 乘法逆元(扩展欧几里得)
  8. Linux文件系统目录结构
  9. C语言常用的字符串函数
  10. Java继承Thread类创建多线程