Spring事务原理笔记

160 阅读5分钟

前言

自己整理的spring 事务的读书笔记

参考

image.png

image.png

image.png

基本知识点

数据库隔离级别说明
ISOLATION_DEFAULT默认使用数据库的隔离级别
ISOLATION_READ_UNCOMMITTED读未提交,存在脏读、幻读、不可重复读
ISOLATION_READ_COMMITTED读已提交,存在幻读、不可重复读
ISOLATION_REPEATABLE_READ可重复读,存在幻读(MySql通过MVCC解决这个问题)
ISOLATION_SERIALIZABLE串行化
事务问题名词解释说明
脏读一个事务读取到了另一个事务未提交的数据
幻读当前事务查询是一条记录,另一个事务提交之后,再次查询是两条记录
不可重复读一个事务读取一个值,当另一个事务提交对这个值修改的事务,当前事务再次读取发生了变化

参考:MySQL事务隔离级别和MVCC - 掘金 (juejin.cn)

事务传播机制说明
PROPAGATION_REQUIRED如果当前没有事务,则新建一个事务;反之,则用当前事务
PROPAGATION_SUPPORTS如果当前有事务,则使用;反之,则无事务
PROPAGATION_MANDATORY以非事务执行,如果当前有事务,则抛异常
PROPAGATION_REQUIRES_NEW如果当前没有事务,则新建一个;反之,则把当前事务挂起,新建一个
PROPAGATION_NOT_SUPPORTED如果当前有事务,则抛异常;反之,则以非事务运行
PROPAGATION_NESTED如果当前已存在一个事务,那么该方法在嵌套事务中运行。 嵌套事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么行为和 PROPAGATION_REQUIRED一样(更具体可以看调用这块)
PROPAGATION_NEVER表示当前方法不运行在事务上下文中。如果当前正有一个 事务在运行,则会抛出异常
@Transactional属性说明
value事务管理器 默认"transactionManager"
transactionManager事务管理器
propagation传播机制 默认PROPAGATION_REQUIRED
isolation隔离基本 默认ISOLATION_DEFAULT
timeout超时时间 默认没有超时时间
readOnly只读 会优化查询 默认false
rollbackFor回滚异常 默认runtimeExcpetion 和 error
noRollbackFor不回滚异常

参考: Spring事务

实例

  • 引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

  • 开启事务
@SpringBootApplication(scanBasePackages = {"com.aop.test"})
@ServletComponentScan
@EnableAspectJAutoProxy(exposeProxy = true)
//开启事务
@EnableTransactionManagement
//mapper scan扫描的包下有service,会导致service重复添加
@MapperScan("com.aop.test.mapper")
public class AopApplication {

    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
    }
}
  • Service实现类
@Service
public class TestDoorServiceImpl extends ServiceImpl<TestDoorMapper,TestDoor> implements ITestDoorService {

    @Transactional(rollbackFor = Exception.class)
    public boolean addTestDoor(TestDoor testDoor) {
        boolean save = this.save(testDoor);
        int i= 1/0;
        return save;
    }

    /**
     * 这种情况下调用就会失效,因为this不是代理的对象
     * @param testDoor
     * @return
     */
    public boolean outFunction(TestDoor testDoor) {
        //this.addTestDoor(testDoor);
        return ((ITestDoorService)AopContext.currentProxy()).addTestDoor(testDoor);
    }
}
  • 调用
@RestController
@RequestMapping("/test")
public class MyController {

    @Autowired
    private ITestDoorService iTestDoorService;

    @GetMapping("/addTest")
    public Boolean saveTestDoor(){
        TestDoor testDoor = new TestDoor();
        testDoor.setOperatorId(22);
        iTestDoorService.outFunction(testDoor);
        return true;
    }
}

初始化

image.png

@EnableTransactionManagement注解中:

  1. 引入TransactionManagementConfigurationSelector
  2. proxyTargeClass标识是否用Cglib代理
  3. mode 标识是使用JDK动态代理 还是使用AspectJ代理

image.png

TransactionManagementConfigurationSelector,在PROXY模式下,通过Selector模式往容器中导入AutoProxyRegistrarProxyTransactionManagementConfiguration

image.png

AutoProxyRegistrar中,往BeanDefinitionRegistry中注册了一个bean, 这个bean就是InfrastructureAdvisorAutoProxyCreator

image.png

这个InfrastructureAdvisorAutoProxyCreator和在Aop中的AnnotationAwareAspectJAutoProxyCreator的继承体系是差不多的,当时Aop的AutoProxyCreator优先级更高,如果同时启用了事务和AOP,那么AnnotationAwareAspectJAutoProxyCreator替代它负责注册adviser和对符合条件的bean代理(其实就是AOP的过程)

image.png

ProxyTransactionManagementConfiguration 在这个类中,注册了BeanFactoryTransactionAttributeSourceAdvisor增强器,而增强其依赖两个bean TransactionAttributeSource(实际返回的是AnnotationTransactionAttributeSource)、 TransactionInterceptor

TransactionAttributeSource的作用类似与PointCut,解析带有@Transactional注解。

image.png

TransactionInterceptor 实现了MethodInterceptor接口(MethodBeforeAdviceInterceptorAspectJAfterAdviceAfterReturningAdviceInterceptorAspectJAfterThrowingAdvice),其实就是事务的通知(advice),采取是一种环绕式通知。

代理

代理过程其实和AOP类似,首先在初始化之前,找到了BeanFactoryTransactionAttributeSourceAdvisor增强器,然后再初始化之后执行时,匹配到复核Advisor切点的bean,对其进行代理。 详细可以文末链接

调用

image.png

我们在controller注入的接口,实际上它实现类,所有这个代理类是通过Cglib代理的。那么它就走的DynamicAdvisedInterceptor得invoke方法,拿到责任链,开始执行。

image.png

image.png

image.png

先调用得就是TransactionInterceptor得invoke方法。

  1. 获取@Transactional注解信息
  2. 获得TransactionManager事务管理器
  3. 创建TransactionInfo事务信息
  4. 环绕式通知
  5. 如果有异常执行
  6. 最终清理事务信息
  7. 如果没有错误执行

image.png

image.png

如果当前不存在事务,则开始新建事务

image.png

如果存在事务,PROPAGATION_NOT_SUPPORTED则是挂起事务(把当前事务放入SuspendedResourcesHolder),以非事务运行。PROPAGATION_REQUIRES_NEW挂起事务,新建一个的事务。

image.png

如果是PROPAGATION_NESTED, 则是嵌套执行,如果事务管理器是Spring事务管理器,则会保存一个savePoint,然后用的同一个事务,如果是Jta事务,则返回一个新的事务。

在构建完成事务信息之后,就开始执行目标类得方法,执行成功,则走TransactionAspectSupport.commitTransactionAfterReturning方法,提交事务。 如果失败,则TransactionAspectSupport.completeTransactionAfterThrowing方法,如果异常是规定得异常或者是默认得异常(RuntimeExcpetion OR ERROR),则回滚。否则提交。

事务不生效场景

  1. Service方法抛出的异常不是RuntimeException或者Error类型,并且@Transactional注解上没有指定回滚异常类型

2.非事务方法直接通过this调用本类事务方法

这个在示例代码有体现,原因是this实际不是AOP代理的类,那么它的方法就不会执行增强的方法。那么可以这样在SpringBoot入口类中通过注解@EnableAspectJAutoProxy(exposeProxy = true)将当前代理对象暴露到AOP上下文中(通过AopContext的ThreadLocal实现)。这个在代理里有写。

同理可得,如果类似上面的这种this方法调用在一个本类的事务方法中调用,那么this方法上你定义的传播机制也不会生效,原因是一样的。如果要实现传播机制,也得使用上面的写法。

总结

Spring Transaction的执行大体是这样,更新详情的可以看看文末的文章,每一步都有详细的讲解。

参考:较真儿学源码系列-Spring事务管理核心流程源码分析_天瑕的博客-CSDN博客