乐于分享
好东西不私藏

Spring——DAO之事务源码

Spring——DAO之事务源码

    每天进步一点点,大家好,我是大龄码农。

    今天我们聊聊事务源码。

    在事务和事务传播这两篇文章中,我们使用@Transactional通过实例展示了实际效果,那么今天我们来讲讲它是如何实现的。

    大家现在对Spring中的事务写法已经很熟悉了,对比Java中的事务写法,我们发现它缺少了三部分:数据库的连接、DML操作的提交或者回滚、数据库的关闭。但是,它依然完成了事务操作,说明Spring做了一些额外的事情。我们已经学过了AOP,@Transactional就是一个很好的AOP应用。

    通过以前的源码分析的文章,想必大家已经对AOP的处理过程比较熟悉了,所以,这里我只列出关于事务处理的部分

    注:源码分析可以参考 AOP之全注解源码探查

    一、几个重要的类

    1、ProxyTransactionManagementConfiguration

        基于注解事务,在Spring容器中注册一些基础的Bean

    注:在@EnableTransactionManagement注解中,引入了TransactionManagementConfigurationSelector类,通过不同的代理方式指定不同的处理类

    说明:继承关系图

    说明1:在处理@Bean注解时,会创建对象。@Role(BeanDefinition.ROLE_INFRASTRUCTURE)注解指明这是一个后台的角色,与终端用户无关

    说明2:这里面有三个类

  • BeanFactoryTransactionAttributeSourceAdvisor
    保存了通知与切点信息
  • TransactionAttributeSource

    保存了切入点信息

  • TransactionInterceptor

    拦截器类,用于代理实际的调用方法

    2、TransactionInterceptor

        这个类是处理@Transactional注解的核心类

    说明:上面是继承关系图

        说明:调用invoke()方法,实际使用的是其父类的方法,TransactionAspectSupport#invokeWithinTransaction()

    3、TransactionAspectSupport  

    说明:先来看看事务管理对象是如何获得的

    说明1:这是determineTransactionManager()方法中,对于默认事务管理对象的处理方式

    说明2:还记得在配置类中定义的JdbcTransactionManager类么

    下面再来看看如何获得数据库连接

    说明:因为满足if的第2个判断条件,所以进入分支。下面看看createTransactionIfNecessary()方法都做了什么

    说明:在getTransaction()方法中,进行了数据库的连接与相关类的初始化操作,最后调用prepareTransactionInfo()方法

    说明:又见到了真正干活的doXXX()方法,这里是为了获取数据源信息

    说明1:从resources字段获取,TransactionSynchronizationManager类定义了很多ThreadLocal类的属性,后面还会看到。

    说明2:使用ThreadLocal类进行线程隔离,对ThreadLocal不熟悉的同学猛戳这里

    说明:从名字就能看出,这是对已有事务连接的处理方法

    说明:大家眼熟吧,事务传播的处理,这里的判断就不一一说了,还是回到主线,其实最后调用的方法几乎是一样的

    说明:这里有两个很重要的步骤

  • 挂起交易

        说明:可以看到,TransactionSynchronizationManager类设置了很多属性值,这个就是前面说的它的很多ThreadLocal类的属性

  • 开始一个新的交易   

    说明:注意newSynchronization这个变量的值,后面很多判断会用到

    先来看下doBegin()方法

    说明:到这里就不用多说了,看到了Connection的设置

    再看下prepareSynchronization()方法

    说明:如果是新的同步信息,就对其进行相关的设置

    好了,回到TransactionAspectSupport#createTransactionIfNecessary()方法的最后一步,继续调用prepareTransactionInfo()方法

    说明:设置当前交易信息的状态

    说明:将交易信息绑定到线程上

    说明:保存已存在的交易对象,为了当前交易完成后的恢复处理

    说明:上面有介绍,这里就不多说了

    好了,回到TransactionAspectSupport#invokeWithinTransaction()方法

    说明:构造一个try-catch-finally的块,通过环绕通知的方式,继续调用实际的代理方法

    先看发生异常的处理

    说明:只对设置的异常才会进行回滚操作,即属性rollbackFor设置的值,对于不满足的情况,依然会提交,这里就不截图了

    再看finally中的处理

    说明:还记得前面绑定线程的处理么,这里就是恢复操作

    最后,如果程序没有抛出异常,会执行提交事务的操作

    说明:这些都是TransactionAspectSupport类提供的模板方法

 这里最后再说下如何关闭连接

    AbstractPlatformTransactionManager类实现了事务的提交与回滚方法

  • commit()--->processCommit()

  • rollback()--->processRollback()

    这两个方法在finally块中都会调用cleanupAfterCompletion()方法

    说明:最后的清理工作。将当前事务状态设置为已完成

    说明:最后的清理工作。释放之前绑定到当前线程的事务属性

    说明:最后的清理工作。重置并释放连接(如果配置的是连接池,就是将连接放回连接池)

    说明:最后的清理工作。恢复此前被挂起的资源和事务属性。

    4、TransactionManagementConfigUtils

配置一些常量,用于Spring在创建bean时,缓存Map中的key

    二、针对配置类的对象创建进行简单的说明

    说明:这是AbstractAutowireCapableBeanFactory#createBeanInstance()方法中,比较靠前的一个判断,通过这次配置类的形式,把这个拿出来说一下

    说明:以这个为例,此时,beanName是“getDruidDataSource”, mbd.getFactoryMethodName()是“MyDbConfig”。尽管在类的定义中,getDruidDataSource()是MyDbConfig类的一个方法,但是由于@Bean注解的加持,它就是一个独立的对象。在后续的处理中也进行了说明

    说明:在ConstructorResolver#instantiateUsingFactoryMethod()方法中处理的

    三、关于数据库的信息是如何处理的

    说明1:在ConfigurationClassParser#doProcessConfigurationClass()方法中处理的

    说明2:如果是使用xml配置的,是通过PropertySourcesPlaceholderConfigurer类处理的

    说明1:在ContextNamespaceHandler类注册的

    说明2:关于数据库配置文件在注解中是如何填充属性的,或者是在配置文件中如何替换占位符并填充属性的部分,有兴趣的同学,可以自己去看下。

    说明3:关注PropertySourcesPropertyResolver类及其父类AbstractPropertyResolver

最后的最后,说下事务失效的几种场景

  • Bean未被Spring容器管理

    加上@Component注解或者在xml文件中配置

  • 数据库不支持事务

比如:MySql中的MyISAM引擎

  • 未开启事务

启用事务注解处理。正确配置事务管理器,注意与数据源的兼容性,同时对于跨多个数据源时,要使用分布式事务管理器

  • 非public修饰的方法或者使用final修饰的方法

    cglib动态代理的实现原理

  • 方法内部调用

    普通方法调用同类中的一个带有注解的方法,其实内部是使用this进行调用的。最简单的解决方法是将方法拆分到不同的类中

  • 事务方法内启动新线程

    不在同一个线程中,不是同一个数据库连接,所以也就不会是同一个事务

  • 事务传播属性问题

使用错误的事务传播属性,比如:Propagation.NEVER

调用方和被调用方的事务传播属性不一致

  • 异常处理不得当

使用try-catch捕获异常后未再次抛出,或者抛出的异常与rollbackFor设置的异常不匹配

  • AOP代理失效

可能因为存在循环依赖导致,通过延迟依赖注入、手动获取代理对象、使用AspectJ模式、使用编程式事务等方式,来绕过Spring代理的限制

  • 事务隔离级别设置不得当

    在并发环境中,当多个线程同时操作共享资源时可能会发生数据的一致性问题。通过适当加锁并尽量减少事务的持续时间等方式,来减少并发操作的影响。

    总结:
    1、介绍了事务源码中涉及的几个重要的类
    2、介绍了事务源码中拦截器是如何工作的
    3、介绍了事务失效的几种场景

相关学习资料