TransactionManagement源码阅读路径前

前言

本文主要记录笔者学习Transaction Management的学习路径,读者可以对比自己的学习路径,一起讨论出探讨出更优学习路径。

基本路径

  1. 找到官方文档。
  2. 找到Overview页面,了解项目提供的能力、能实现的效果、设计理念、历史、最低要求等等。
  3. 找到Quick Start,快速把它的环境搭建起来,对项目有个感性的认知。
  4. 找到想了解的模块
  5. 找到模块的Introduction,一般这里有项目的基本介绍,你需要找到这个模块用到的基本概念或者名词介绍文档。
  6. 使用场景、功能特性以及相关生态系统介绍
  7. 了解核心/共用/复用模块
  8. 确定学习目标(遇到的问题、心存疑问/好奇),以点带面的阅读方式来阅读源码
  9. 提炼升华
  10. 结合实际开发/项目,可以解决什么问题、原来处理有什么不足/缺陷
  11. 日常思考、联想、完善

接下来将根据基本路径展开,源码时序图只画出核心点,具体还需读者自行调试。

Spring Framework Overview

参考

Quick Start

传送门

想要了解的模块

Transaction Management

使用场景和功能特性

Spring Framework全面支持事务,为事务管理提供了一致性的抽象。跨不同事务API的一致性编程模型。支持声明式事务管理与Spring的数据库访问抽象的完美集成

核心

通过阅读前面文章,使用声明式事务的话,可以大概知道Spring Framework的核心有事务策略的抽象-PlatformTransactionManager、管理每个线程的资源和事务同步的中央委托-TransactionSychrnoizationManager、使用AOP代理包装合适的bean的一种BeanPostProcessor-AbstractAutoProxyCreator、配合PlatformTransactionManager实现驱动事务around method的TransactionInterceptor

下面将通过一个简单的事务示例(使用mysql、mybatis-plus、spring-boot),画一个时序图来看看核心类起到的作用。

示例

import lombok.SneakyThrows;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
@MapperScan(basePackages = {"com.whf.spring.dao"})
public class Application {
    @SneakyThrows
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

import com.whf.spring.service.business.TryService;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
class ApplicationTests {
    @Autowired
    private TryService tryService;

    @Test
    void testTransaction() {
        tryService.test();
    }
}
复制代码
import com.whf.spring.annotation.SimpleAnnotation;
import com.whf.spring.service.UserService;
import com.whf.spring.service.business.TryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Scope
@Slf4j
public class TryServiceImpl implements TryService {
    @Autowired
    private UserService userService;

    @Override
    @SimpleAnnotation
    @Transactional(rollbackFor = Exception.class)
    public void test() {
        log.info("执行业务");
        userService.test();
    }
}
复制代码
import com.whf.spring.dao.UserDao;
import com.whf.spring.entity.UserDo;
import com.whf.spring.service.UserService;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;

@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserDao, UserDo> implements UserService {
    @Override
    public void test() {
        this.selectById(1);
        throw new RuntimeException();
    }
}
复制代码

大致调用如下:

image.png

源码时序图

核心的顺序应该是:

  • AbstractAutoProxyCreator 创建代理
  • 调用事务必经过TransactionInterceptor
  • TransactionInterceptor会调用PlatformTransactionManger进行事务管理
  • PlatformTransactionManger会调用TransactionSychrnoizationManager进行事务资源同步

核心类的源码如下,围绕核心的一些其他源码时序图在其他章节会涉及。

创建代理时序图

image.png

可以看到,代理的生成发生在初始化后期,@Transactional是可以放在接口上的,@Transactional只有放在public方法上才生效,最终使用的是cglib代理而不是jdk动态代理。

调用时序图

image.png

调用主要围绕TransactionInterceptor展开,around事务业务方法进行一些处理,例如before method时获得TransactionAttributeSource、确定具体的TransactionManager、根据事务上下文和传播行为决定是否创建新的事务等、绑定资源、解绑资源。

确定学习目标

遇到问题

  • EnableTransaction使用默认的PROXY基于接口的代理,为啥不实现接口@Transactonal也可以用

详见下面@EnableTransactionManagement源码章节

  • 每次都要学rollbackFor,怎么全局写

可以自定义事务注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
public @interface DefaultTransactional {
}
复制代码

心存疑问/好奇

  • @EnableTransactionManagement属性的影响?

详见下面“@EnableTransactionManagement”源码章节

  • 如何注入被事务代理的bean?

详见下面“事务代理bean的注入”源码章节

  • Spring声明式事务是如何实现的?

详见下面“Spring声明式事务的具体实现”章节

  • Spring如何实现对资源的管理(创建、重用和清理)
    详见下面“Spring如何实现对资源的管理”源码章节

  • Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的
    详见下面“Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的”源码章节

  • @TransactionalEventListeners是怎么实现的
    详见下面“@TransactionalEventListeners是怎么实现的”源码章节

  • 事务切面和普通定义的@Aspect切面的先后顺序
    详见下面“事务切面和普通定义的@Aspect切面的先后顺序”源码章节

学习目标涉及的源码

@EnableTransactionManagement

使用的还是核心中的代码示例

image.png

属性影响

整体可以看出@EnableTransactionManagement会影响全局的APC选择,还会影响
transactionAdvisor的顺序,方便处理存在多个advisor。

为啥可以不实现接口@Transactonal也可以用

至于为啥@EnableTransactionManagement默认AdviceMode.PROXY、proxyTargetClass=false不实现接口还能使用@Transactional是因为最终选择的APC为AnnotationAwareAspectJAutoProxyCreator。

为啥不在Application上注解@EnableTransactionManagement也会使用上事务

按照上面的时序图调试就可以发现,AdviceModeImportSelector#selectImports的入参不一样,加的话如上面的代理示例@Configuration就是我们自己写的Application,不加的话@Configuration就是TransactionAutoConfiguration springboot自动装配的。

事务代理bean的注入

image.png

发现和普通的注入类似。注入的tryService是提前被代理好的bean。

Spring声明式事务的具体实现

结合上面的创建代理时序图、调用时序图、@EnableTransactionManagement时序图,我们来大体梳理下。

  1. 首先需要在@Configuration上使用@EnableTransactionManagement注解,不管是在自动装配还是在你自己的@Configuration。这样相当于xml的<tx:annotation-driven/>配置,表示启用基于事务注解的事务行为。同时确定了APC为AnnotationAwareAspectJAutoCreator、事务管理配置为ProxyTransactionManagementConfiguration。
  2. ProxyTransactionManagementConfiguration确定了advisor为BeanFactoryTransactionAttributeSourceAdvisor、advice为TransactionInterceptor、事务属性资源AnnotationTransactionAttributeSource。这样就能使用BeanFactoryTransactionAttributeSourceAdvisor生成事务代理。
  3. TransactionInterceptor设置了TransactionManager对事务进行管理,TransactionManager会使用到TransactionSynchronizationManager对资源进行同步。

Spring如何实现对资源的管理

首先要明白管理的对象是谁,下面用JDK原生写一个执行sql

Class.forName("com.mysql.jdbc.Driver"); // 加载驱动
conn = DriverManager.getConnection(url); // 获取数据库连接
stmt = conn.createStatement(); // 创建执行环境
stmt.execute(sql); // 执行SQL语
复制代码

主要就是对Connection的管理,创建连接、重用连接、清理连接。具体就是TransactionManager和TransactionSynchronizationManager的配合,具体可以根据上面的调用时序图进行了解。

Spring如何处理物理事务和逻辑事务,不同的propagation是怎么处理的

Spring事务底层是依赖于例如mysql之类的物理事务,一个Connection开启一个物理事务,逻辑事务是Spring根据Propagation来决定当前事务的行为(比如要挂起当前事务开启新的事务,还是加入当前事务),根据业务逻辑控制物理事务。一个物理事务对应一个Connection,一个Connection可能存在多个逻辑事务。

默认的Propagation.REQUIRED处理在上面的“调用时序图”可以了解到,下面将对常用的Propagation.PROPAGATION_NESTED传播行为进行讲解,通过对比默认的传播行为来更加清楚Spring对逻辑事务的处理,同时也能对事务回滚了解一二。具体如下图:

image.png

可以看到PROPAGATION_NESTED不会创建一个新的事务会对现有事务进行处理,更改TransactionStatus属性特别是savepoint,最后执行底层数据的“ROLLBACK TO SAVEPOINT”语句进行局部回滚(说明此种传播行为需要底层数据的支持),然后回到外层invokeWithTransaction,如果外层事务不想回滚直接catch住就可以了。

@TransactionalEventListeners是怎么实现的

image.png

TransactionalEventListener是一种ApplicationEventMulticaster会注册到TransactionSynchronizationManager中,TransactionSynchronizationManager提供一些事务钩子便可以根据事务不同阶段执行你发布的TransactionalEvent。

事务切面和普通定义的@Aspect切面的先后顺序

image.png

原始bean初始化之后会调用BeanFactory的applyBeanPostProcessorsAfterInitialization看是否有合适被代理,通过AnnotationAwareAspectJAutoProxyCreator找到所有拦截器(BeanFactoryTransactionAttributeSourceAdvisor 、InstantiationModelAwarePointcutAdvisor、ExposeInvacationInterceptor)并根据advisor的order进行排序生成一个具有多个advisor的代理。