Spring事务管理的底层逻辑—源码解析

3,026 阅读5分钟

著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

本文代码为spring 5.1.2

spring是如何控制事务的提交和回滚

加上@Transactional注解之后,Spring可以启到事务控制的功能,在正式执行方法前它会做一系列的操作,我们来看看在这之中它到底都做了些什么?

首先进入CglibAopProxy.classintercept方法打上一个Debug断点调试,或者在JdkDynamicAopProxy.classinvoke方法(如果目标方法是继承接口方式实现),根据不同实现方法类型选择不同的动态代理类。不了解的同学可以查看我的另一篇文章《通过Spring Aop 了解动态代理》

因为源码过于繁长,我将以图文的形式进行讲解,便于阅读理解
CglibAopProxy.class

JdkDynamicAopProxy.class
此处动态代理具体是指代的目标对象和目标方法,通过下图debug Evaluate看出

进去之后来到ReflectiveMethodInvocation.class然后一直往下走来到invoke方法

进入invoke方法来到 TransactionInterceptor.class

继续往下,进入invokeWithinTransaction()方法,来到 TransactionAspectSupport.classinvokeWithinTransaction()方法,

进入createTransactionIfNecessary(tm, txAttr, joinpointIdentifcation)方法,如图

出现异常时进入completeTransactionAfterThrowing()方法,可以看到其中调用了rollback()回滚

没有异常则继续往下执行commitTransactionAfterReturning()

以上只是解读了大致的处理流程,由于没有画流程图,可能有的读者光看文章会觉得脑回路不清晰,建议大家在阅读文本之后也去自己的项目当中浏览一下,理清逻辑顺序,如果有小伙伴想提供本文流程图,欢迎你!

@Transactional 事务注解是如何产生作用的?

如上所说,事务最开始是先进入CglibAopProxy.classintercept()方法或者JdkDynamicAopProxy.classinvoke()方法,可以看出这2个类都是AOP增强类,所以@Transactional 事务注解才可以产生作用。

来到TransactionAspectSupport.classcreateTransactionIfNecessary()方法

进入tm.getTransaction(txAttr); 来到AbstractPlatformTransactionManager.class

@Transactional注解内容如下, 可以看到 Propagation属性的默认值为 REQUIRED

如果当前已经存在事务,进入handleExistingTransaction方法,如图

简单时序图

有哪些不同类型的事务

Spring中的事务传播属性

  • 挂起:在新方法里面,不使用外部的事务环境
  • 嵌套事务:一个事务里面包含另一个事务
  • 保存点:一个事务新起时的保存记录点

事务类型不同会有什么不同效果

REQUIRES_NEWNESTED 为例,来讲讲嵌套事务和保存点的概念。

如下是一段伪代码

情景一:

@Transactional   // 默认事务级别
buyShopping(){
    insertOrder();
    stockService.updateStock();
}
@Transactional(propagation = Propagation.REQUIRES_NEW) 
// 如果使用该级别,库存扣减回滚,插入订单也回滚(使用的保存点)
@Transactional(propagation = Propagation.NESTED) 
updateStock(){
    // 省略业务代码
    // 商品库存减 1
    throw // 如果使用该级别,库存扣减回滚,插入订单成功
Exception;
}

情景二:

@Transactional   // 默认事务级别
buyShopping(){
    insertOrder();
    stockService.updateStock();
    throw Exception;
}
// 如果使用该级别,库存扣减成功,插入订单失败回滚(新事务)
@Transactional(propagation = Propagation.REQUIRES_NEW)  
// 如果使用该级别,插入订单失败回滚,库存扣减同样回滚(savePoint机制)
@Transactional(propagation = Propagation.NESTED) 
updateStock(){
    // 省略业务代码
    // 商品库存减 1
}

关于嵌套事务的更多概念可以参考==>
Spring 采用保存点(Savepoint)实现嵌套事务原理
嵌套事务的使用

注意事项

  • 不要随意使用事务的传播属性
  • 不影响事务的业务代码,请使用try/catch处理
  • 影响事务的业务代码,可以抛出异常使其回滚

Spring是如何控制事务的提交与回滚

事务要保证ACID的完整性必须依靠事务日志做跟踪,每一个操作在真正写入数据数据库之前,先写入到日志文件中,如要删除一行数据会先在日志文件中将此行标记为删除,但是数据库中的数据文件并没有发生变化。只有在(包含多个sql语句)整个事务提交后,再把整个事务中的sql语句批量同步到磁盘上的数据库文件

在事务引擎上的每一次写操作都需要执行两遍:

  1. 先写入日志文件中,写入日志文件中的仅仅是操作过程,而不是操作数据本身,所以速度比写数据库文件速度要快很多.
  2. 然后再写入数据库文件中,写入数据库文件的操作是重做事务日志中已提交的事务操作的记录.

日志组

一般不止设置一个日志文件,一个文件写满之后使用另外一个日志文件提高服务器效率。 日志文件的日志同步到磁盘后空间会自动释放,单个日志文件不宜设置过大,如果日志文件过大mysql进程在把日志同步到数据文件的时候可能会崩溃

事务日志用途

事务日志可以帮助提高事务的效率,使用事务日志,存储引擎在修改表的数据的时候只需要修改其内存拷贝,再把该行为记录到持久在磁盘的事务日志中。而不用每次都将修改的数据本身持久到磁盘事务日志采用的是追加方式,因此写日志的操作是磁盘上一小块区域的顺序IO,而不像随机IO需要磁盘在多个地方移动。所以采用事务日志的方式相对来说要快的多,事务日志持久后,内存中的修改在后台慢慢的刷回磁盘。期间如果系统发生崩溃,存储引擎在重启的时候依靠事务日志自动恢复这部分被修改数据。

更多内容可以参考==> 详细分析MySQL事务日志(redo log和undo log)