java MySQL Spring

A. 什么是事务的ACID?

ACDI是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)

原子性(Atomic)

原子性,就是一堆SQL,要么一起成功,要么都别执行,不允许某个SQL成功了,某个SQL失败了,这就是扯淡,不是原子性

一致性(Consistency)

这个是针对数据一致性来说的,就是一组SQL执行之前,数据必须是准确的,执行之后,数据也必须是准确的。别搞了半天,执行完了SQL,结果SQL对应的数据修改没给你执行,那不是坑爹么。

隔离性(Isolation)

这个就是说多个事务在跑的时候不能互相干扰,别事务A操作个数据,弄到一半儿还没弄好呢,结果事务B来改了这个数据,导致事务A的操作出错了,那不就搞笑了。

持久性(Durability)

事务成功了,就必须永久对数据的修改是有效的,别过了一会儿数据自己没了,不见了,那就好玩儿了

B.事务的隔离级别

由于上面的事务的ACID特性引申出数据库中事务的隔离级别,MySQL有4个事务隔离级别,他们分别如是:

读未提交(Read Uncommitted)

就是说某个事务还没提交的时候,修改的数据,就让别的事务给读到了,这就恶心了,很容易导致出错的。这个也叫做脏读。

读未提交 —> 会出现脏读

脏读(Dirty Read)

一个事务读到了另一个未提交事务修改过的数据

image.png

读已提交(Read Committed)

就是一个事务要等另一个事务提交后才能读取数据。这个比上面(读未提交)稍微好一点,但是一样比较尴尬,就是说事务A在跑的时候, 先查询了一个数据是值1,然后过了段时间,事务B把那个数据给修改了一下还提交了,此时事务A再次查询这个数据就成了值2了,这是读了人家事务提交的数据,就是所谓的一个事务内对一个数据两次读,可能会读到不一样的值。
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle
image.png

可重复读(Read Repeatable)

就是在开始读取数据(事务开启)时,不再允许修改操作。这个就是比(读已提交)再好点儿,就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B修改了数据的值还提交了,但是事务A读到的还是自己事务开始时这个数据的值。
同时,MySQL默认隔离级别就是可重复读而其中MySQL实现可重复读是通过MVCC机制来实现的,下面章节会说到这个!
image.png

串行化也称为序列化(Serializable)

最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。比如某个事务把所有行的某个字段都修改为了2,结果另外一个事务插入了一条数据,那个字段的值是1,然后就尴尬了。第一个事务会突然发现多出来一条数据,那个数据的字段是1。如果要解决幻读,就需要使用串行化级别的隔离级别,所有事务都串行起来,不允许多个事务并行操作。
image.png

C.MVCC机制

上面介绍可重复读带到是通过MVCC机制来实现的,那下面具体分析什么是MVCC机制及其联系MySQL。
MVCC全称:Multi-Version Concurrency Control,是指多版本并发控制。MVCC是在并发访问数据库时,通过对数据进行多版本控制,避免因写锁而导致读操作的堵塞,从而很好的优化并发堵塞问题。

1.MVCC是什么?

MVCC是指多版本并发控制。MVCC是在并发访问数据库时,通过对数据进行多版本控制,避免因写锁而导致读操作的堵塞,从而很好的优化并发堵塞问题。解决并发问题的通用方案有:
(1)对并发访问的数据添加一把排它锁,添加锁之后,其他的读和写操作都需等待锁释放后才能访问。
(2)添加一把共享锁,读读操作不需要等待锁的释放,读写和写写操作需要等待锁的释放。
(3)通过对并发数据进行快照备份,从而达到无锁数据的并发访问。
通俗的讲就是MVCC通过对数据进行多版本保存,根据比较版本号来控制数据是否展示,从而达到读取数据时无需加锁就可以实现事务的隔离性。

总结:

MVCC:

  • innodb存储引擎,会在每行数据的最后加两个隐藏列,一个保存行的创建时间,一个保存行的删除时间,但是这儿存放的不是时间,而是事务id,事务id是mysql自己维护的自增的,全局唯一
  • 在一个事务内查询的时候,mysql只会查询创建时间的事务id小于等于当前事务id的行
  • 子主题

MVCC机制:多版本并发控制机制

  • undo log多版本链条+ReadView机制
  • ReadView:基于undo log版本链条实现的一套试图机制,ReadView一旦生成就不会改变
  • RC隔离级别:的关键点在于,事务里每次查询都生成新的ReadView
  • RR隔离级别:的关键点在于,事务里每次查询都是同一个ReadView

    2.MVCC的实现原理

    MVCC的两个实现核心是undo log一致性视图,通过undo log来保存多版本的数据,通过一致性视图来保存当前活跃的事务列表,将两者结合和制定一定的规则来判断当前可读数据。
    我们举个例子来分析下MVCC的具体流程:
    2.事务特性、MySQL事务及Spring事务的支持 - 图5

    3. undo log是什么?它在MVCC中启到什么作用?

    innodb在修改数据库数据记录之前会先在undo log中记录回滚日志,日志的内容为:
    执行insert时会对应在undo log中记录一条delete语句,并且会记录这个版本的事务id(txid),执行update语句会有一条update语句来使之数据恢复到上个版本。
    undo log主要用于事务回滚和mvcc获取不同事务id对应的数据来实现事务隔离。
    undo log日志的存储跟redo log有些区别,undo通过段的形式存储在共享表空间中,redo log存储于日志文件中。
    undo log的内容变更也会记录到redo log中,从而实现undo log的持久化。
    总结:undo log实现了数据库快照功能,通过事务id和undo log我们可以找到历史版本的数据

    4.一致性视图是什么?

    InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交。简而言之一致性视图是个数组,记录了当前活跃的事务ID,通过这个数据我们可以判断出来事务执行的先后顺序,事务所能读取的数据版本。
    在innodb 中每个SQL语句执行前都会得到一个read_view。副本主要保存了当前数据库系统中正处于活跃(没有commit)的事务的ID号,其实简单的说这个副本中保存的是系统中当前不应该被本事务看到的其他事务id列表。
    数组里面事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位。
    这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)
    2.事务特性、MySQL事务及Spring事务的支持 - 图6

一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  1. 版本未提交,不可见;
  2. 版本已提交,但是是在视图创建后提交的,不可见;
  3. 版本已提交,而且是在视图创建前提交的,可见。

通过上述的概念我们来解下开始的例子:
2.事务特性、MySQL事务及Spring事务的支持 - 图7

假设当前有个正在执行事务99,数据行的历史版本为事务id90(1,1)。
(1)按照事务的开启时间,分别递增分配了100,101,102三个事务ID(trx id)
(2)在SQL语句执行之前,事务A生成一致性视图【99,100】,事务B生成一致性视图【99,100,101】,事务C生成一致性视图【99,100,101,102】
(3)SQL语句的执行之前生成undo log,通过undo log可以生成历史版本数据快照,上图右侧历史版本数据。
(4)事务A的查询执行时,当前数据版本为trx id:101,跟一致性视图【99,100】进行比较,101大于高水位不可见,通过undo log回退到trx id:102版本,102也大于高水位不可见,再回退一个版本到trx id:90,90低于低水位可见,所以事务A读取到的数据为(1.1)。

5.从3-4归纳

innodb存储引擎,会在每行数据的最后加两个隐藏列,一个保存行的创建时间,一个保存行的删除时间,但是这儿存放的不是时间,而是事务id,事务id是mysql自己维护的自增的,全局唯一
比如:事务id,在mysql内部是全局唯一递增的,事务id=1,事务id=2,事务id=3

id name 创建事务id 删除事务id
1 张三 120 122
2 李四 119
2 小李四 122
  1. 事务id=121的事务,查询id=1的这一行的时候,一定会找到创建事务id <= 当前事务id的那一行,select * from table where id=1,就可以查到上面那一行
  2. 事务id=122的事务,将id=1的这一行给删除了,此时就会将id=1的行的删除事务id设置成122
  3. 事务id=121的事务,再次查询id=1的那一行,能查到吗?能查到,要求创建事务id <= 当前事务id,当前事务id < 删除事务id
  4. 事务id=121的事务,查询id=2的那一行,查到name=李四
  5. 事务id=122的事务,将id=2的那一行的name修改成name=小李四
  6. 事务id=121的事务,查询id=2的那一行,答案是:李四,创建事务id <= 当前事务id,当前事务id < 删除事务id

在一个事务内查询的时候,mysql只会查询创建时间的事务id小于等于当前事务id的行,这样可以确保这个行是在当前事务中创建,或者是之前创建的;同时一个行的删除时间的事务id要么没有定义(就是没删除),要么是必当前事务id大(在事务开启之后才被删除);满足这两个条件的数据都会被查出来。
那么如果某个事务执行期间,别的事务更新了一条数据呢?这个很关键的一个实现,其实就是在innodb中,是插入了一行记录,然后将新插入的记录的创建时间设置为新的事务的id,同时将这条记录之前的那个版本的删除时间设置为新的事务的id。
现在get到这个点了吧?这样的话,你的这个事务其实对某行记录的查询,始终都是查找的之前的那个快照,因为之前的那个快照的创建时间小于等于自己事务id,然后删除时间的事务id比自己事务id大,所以这个事务运行期间,会一直读取到这条数据的同一个版本。

D.Spring的事务及传播特性(分析 @Transactional 注解)

0.Demo的地址

https://gitee.com/ZIB/data-refill-center.git

1.一道面试题让你进入了解Spring的事务理解

面试题:执行某个操作,前50次成功,第51次失败。a 全部回滚;b 前50次提交,第51次抛异常。ab场景分别如何设置spring事务? 答案后面的教程之后你会清晰怎么回答

这道面试题目主要考察的是Spring的事务传播机制。

这个结合@Transactional注解及Demo中DataRefillCenterController#payForDataRefill()方法理解 结论:payForDataRefill()方法内,在没有事务注解下,当中某个方法出现异常不能全部回滚,有脏读的问题出现。

所以添加@Transactional注解,根据阿里编码规范,一般建议加在方法级别:就是要事务的方法就加事务,不要事务的方法就别加事务上。如果在类添加@Transactional注解,表示类里所有方法都开启了事务。(我们要根据情况去添加注解的位置,某些读方法确实不需要添加注解)
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)

  • rollbackFor 属性表示指定哪些异常类型才要回滚事务
  • propagation 属性就是我们本届的重点,Spring事务传播机制属性配置。默认情况是Propagation.REQUIRED

这个Spring事务传播机制就是处理用一句通俗的话来说就是:一个加了@Transactional的事务方法,和嵌套了另外一个@Transactional的事务方法的时候,包括再次嵌入@Transactional事务方法的时候,这个事务到底要如何处理的问题。可能这么说还是比较难理解,结合下面伪代码

  1. public class ServiceA {
  2. @Autowired
  3. private ServiceB b;
  4. @Transactional
  5. public void methodA() {
  6. // 一坨数据库操作
  7. for (int i = 0; i < 51; i++) {
  8. try {
  9. b.methodB();
  10. } catch (Exception e) {
  11. // 打印异常日志
  12. }
  13. }
  14. // 一坨数据库操作
  15. }
  16. }
  17. public class ServiceB {
  18. @Transactional(propagation = PROPAGATION_REQUIRES_NEW)
  19. public void methodB() throws Exception {
  20. // 一坨数据库操作
  21. }
  22. }

当中ServiceB中的methodB()方法在ServiceA的methodA()方法体内递归,加入当中其重一次for循环下b.methodB()异常,那么这里面具体Spring会如何处理呢?具体就是上面的情况当你propagation是取值哪个之后具体就知道Spring如何处理了

传播行为 意义
PROPAGATION_REQUIRED
(spring默认事务传播方式)
如果ServiceA.method调用了ServiceB.method,如果ServiceA.method开启了事务,然后ServiceB.method也声明了事务,那么ServiceB.method不会开启独立事务,而是将自己的操作放在ServiceA.method的事务中来执行,ServiceA和ServiceB任何一个报错都会导致整个事务回滚。
(总结:methodA与methodB方法有@Transactional注解时候,methodB交给methodA【最上头的注解方法处理事务】,当中出现异常就会全部回滚)
PROPAGATION_SUPPORTS 如果ServiceA.method开了事务,那么ServiceB就将自己加入ServiceA中来运行,如果ServiceA.method没有开事务,那么ServiceB自己也不开事务
(总结:1. methodA与methodB方法有@Transactional注解时候,methodB交给methodA
2. methodA没有@Transactional注解,methodB有, 他们都不开启事务
PROPAGATION_MANDATORY 表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常。
(这个比较冷门,没有使用过)
PROPAGATION_REQUIRES_NEW ServiceB.method强制性自己开启一个新的事务,然后ServiceA.method的事务会卡住,等ServiceB事务完了自己再继续。这就是影响的回滚了,如果ServiceA报错了,ServiceB是不会受到影响的,ServiceB报错了,ServiceA也可以选择性的回滚或者是提交。
(总结:使用这个时候,可能出现依赖方法报错不能全过程不能回滚)
PROPAGATION_NOT_SUPPORTED ServiceB.method不支持事务,ServiceA的事务执行到ServiceB那儿,就挂起来了,ServiceB用非事务方式运行结束,ServiceA事务再继续运行。这个好处就是ServiceB代码报错不会让ServiceA回滚
(总结:ServiceB.method用了该修饰,在ServiceA.method进去该方法之前和离开该方法之后,ServiceA.method的事务有工作,其他时间挂起,而ServiceB.method执行异常无法回滚,其余由ServiceA.method的事务处理)
PROPAGATION_NEVER ServiceB.method用了该修饰,那么ServiceA.method调用时候会报错
PROPAGATION_NESTED 开启嵌套事务,ServiceB开启一个子事务,如果回滚的话,那么ServiceB就回滚到开启子事务的这个save point

2.得出的结论【Spring事务传播及@Transactional使用方式】

回应上面的问题,结合Demo中的方法,我们可以知道的是:
在一些方法中涉及多个接口写入操作处理,事务传播选取会变得异常重要,在一个事务当中嵌套几个事务处理,如何解决回滚不出现脏数据尤为关键。所以一般情况下会对多个写入接口封装到同一个类内,使用@Transactional注解封装类,当中使用上面对应适当的Spring事务传播方式(一般都是PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW【递归比较适合】)结合使用

E. Spring AOP与事务的结合

AOP : 一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理:提供接口的代理,核心 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke() 方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
CGLIB动态代理:如果代理类没有实现 InvocationHandler接口,那么Spring AOP会选择使用CGLIB来动态代理生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

1. 这个与事务的有什么联系?

image.png
Demo中DataRefillCenterControllerpayForDataRefill() 方法内比如执行 refillDataCenterService.finishRefillData() 接口时候,在Spring中其实 refillDataCenterService 接口已经被Spring封装的代理,而不是 RefillDataCenterService 本身,而当中payForDataRefill() 方法会发现其有 @Translational 注解,则对这个代理插入一堆事务管理的增强逻辑,开始执行方法之前先开启事务,方法中有报错就回滚事务,如果没有报错就提提交事务
image.png

2.Spring事务核心源码初步分析

  • TransactionIntercepor其实就是给我们的service组件加了@Transactional注解之后,就会对我们的这个service组件里的方法在调用的时候,就会先走这个TransactionoIntercepor的拦截。事务拦截器拦截@Transactional注解标注的类中的方法的调用,finishRfillData()方法之前,就会先走这个拦截器
  • TransactioanIntercepor,这个是事务执行的核心类,调用TransactionIntercepor.invoke()方法
  • TransactionIntercepor.invoke()方法,这个里面调用了invokeWithinTransaction()方法,核心的事务控制的逻辑都在invokeWithinTransaction()方法中,invokeWithinTransaction()方法其实是父类TransactionAspectSupport(Aspect这个名词就可以看出来了,这个东西一定是跟Spring AOP机制是有关系的,Aspect切面的意思,就是spring AOP的核心概念) ```java

    1.TransactionInterceptor类继承了TransactionAspectSupport这个spring AOP的核心类、和方法拦截接口MethodInterceptor(实现invoke方法)

    public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable

2TransactionInterceptor类的invoke方法

public Object invoke(final MethodInvocation invocation) throws Throwable { Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

  1. //invokeWithinTransaction()方法,事务的核心
  2. return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
  3. @Override
  4. public Object proceedWithInvocation() throws Throwable {
  5. return invocation.proceed();
  6. }
  7. });

}

3.以下代码是代理和事务的结合,链接下面debug的截图分析

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
   //1.如果你使用了@Transactional注解之后,意思就是要给这个方法开启事务,此时就会给你开启一个事务,创建事务
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    Object retVal = null;
    try {

        //2.这行代码,其实相当于是去调用你的那个RefillDataCenterService.finishRefillData()方法
        retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
      //3.下面代码表示如果执行上面代理方法报错了,事务回滚
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    }
    finally {
        cleanupTransactionInfo(txInfo);
    }
    commitTransactionAfterReturning(txInfo);
    return retVal;
}

else {
    //代码省略.......
}

}

**i. **postman调用了地址: `PUT /dataRefillCenter/payForDataRefill HTTP/1.1` 方法,进入 `refillDataCenterService``.finishRefillData(refillRequest)` 时候,被`TransactioanIntercepor`代理器拦截,进入invoke方法,下面截图是已经进入该方法后,再进入到`invokeWithinTransaction()`方法内。   <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627295577311-d41b80ec-b952-4192-9a58-3bded9c8cd65.png#crop=0&crop=0&crop=1&crop=1&height=654&id=x5w86&margin=%5Bobject%20Object%5D&name=image.png&originHeight=654&originWidth=1037&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104180&status=done&style=none&title=&width=1037)<br />**ii. **当执行到该代码` TransactionInfo txInfo = createTransactionIfNecessary(tm``, ``txAttr``, ``joinpointIdentification) `的时候,其实给要给`com.zhss.data.refill.center.service.impl.RefillDataCenterServiceImpl.finishRefillData`方法开启事务,此时就会给你开启一个事务(txInfo 对象构建之后,debug下看见其事务相关配置已经构建完成)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627296251890-017c6979-99a5-44b7-a000-8e68ae414835.png#crop=0&crop=0&crop=1&crop=1&height=692&id=yhl4A&margin=%5Bobject%20Object%5D&name=image.png&originHeight=692&originWidth=1019&originalType=binary&ratio=1&rotation=0&showTitle=false&size=124028&status=done&style=none&title=&width=1019)<br />**iii. **执行 `retVal = invocation.proceedWithInvocation() `代码,代理执行调用了`RefillDataCenterServiceImpl.finishRefillData`方法

- 调用前:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627296489807-59e10a46-f23e-4af1-9602-a193015128f5.png#crop=0&crop=0&crop=1&crop=1&height=49&id=EQcfh&margin=%5Bobject%20Object%5D&name=image.png&originHeight=49&originWidth=993&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7045&status=done&style=none&title=&width=993)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627296588872-868395b7-4524-4561-9b9d-59e801fb911d.png#crop=0&crop=0&crop=1&crop=1&height=93&id=EnMSf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=93&originWidth=995&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14841&status=done&style=none&title=&width=995)

- 调用后:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627296562762-e6aa4847-9408-4087-9c61-401dffe4e9cd.png#crop=0&crop=0&crop=1&crop=1&height=91&id=LanoD&margin=%5Bobject%20Object%5D&name=image.png&originHeight=91&originWidth=1016&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11654&status=done&style=none&title=&width=1016)<br />**iv**. 假如`RefillDataCenterService.finishRefillData()`方法报错了,需要回滚执行`completeTransactionAfterThrowing(txInfo, ex)`<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627297244003-645c4091-525c-428b-a215-5e61cf5897b5.png#crop=0&crop=0&crop=1&crop=1&height=332&id=uC6zE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=332&originWidth=969&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33687&status=done&style=none&title=&width=969)<br />**V**. 执行完成没问题就直接通过`commitTransactionAfterReturning(txInfo)`方法提交事务,完成流程!<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1627297370628-444425cd-1536-4d20-a2b0-c10b79c4f7e8.png#crop=0&crop=0&crop=1&crop=1&height=297&id=SUEoU&margin=%5Bobject%20Object%5D&name=image.png&originHeight=297&originWidth=979&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27043&status=done&style=none&title=&width=979)
<a name="76h31"></a>
## 3.Spring事务源码核心分析之基于基于hibernate和jdbc完成事务的开启
> 针对上面  `2.Spring事务核心源码初步分析` 当中进入`TransactionInterceptor.invoke()`方法 --> 执行的`invokeWithinTransaction()`方法分析

<a name="affyo"></a>
### 1) 事务是如何开启的
```java
#1. 事务的开始于该方法
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

2). 事务是如何执行的

针对上面 2.Spring事务核心源码初步分析 当中执行的invokeWithinTransaction()方法内的 retVal = invocation.proceedWithInvocation() 分析

3)2流程执行完成后提交事务

针对上面 2.Spring事务核心源码初步分析 当中执行的invokeWithinTransaction()方法内的 completeTransactionAfterThrowing(txInfo, ex) 分析

截取资料

一文搞懂InnoDB MVCC机制 Spring AOP 扫盲 Spring的AOP理解 Spring事务流程流程图 - 1(参考) Spring事务的底层实现流程 - 2 (参考) Spring事务分析 - 3 (参考) Spring事务分析 -4 (参考)