首页 > 其他分享 >spring事务原理

spring事务原理

时间:2024-05-18 23:31:06浏览次数:25  
标签:事务 spring Transactional sql 原理 执行 方法 数据库

1、事务概念

事务,就是一组操作数据库的动作集合,要么全部成功,要么全部失败。

spring 支持两种方式的事务:

编程式事务

编程式事务管理使用 TransactionTemplate,需要显式执行事务,比如,需要显示调用commit或者rollback方法。

声明式事务

声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

2、声明式事务

2.1、使用方式

使用@EnableTransactionManagement来开启spring事务功能(SpringBoot中不用显示设置)

使用@Transactional注解来标记某个方法使用事务功能,作用在类上则所有方法都使用事务

2.2、事务实现原理

(1)@EnableTransactionManagement做了什么?

开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向Spring容器中添加了两个Bean:

  • AutoProxyRegistrar

  • ProxyTransactionManagementConfiguration

AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。

ProxyTransactionManagementConfiguration是一个配置类,它又定义了另外三个bean:

  • BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor

  • AnnotationTransactionAttributeSource:

    相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut

  • TransactionInterceptor:

    相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice

AnnotationTransactionAttributeSource就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的。

TransactionInterceptor就是代理逻辑,当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法。

(2)spring事务的基本执行原理

一个Bean在执行Bean的创建生命周期时,会经过InfrastructureAdvisorAutoProxyCreator的初始化后的方法,会判断当前Bean对象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象。

该代理对象在执行某个方法时,会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke()方法,执行基本流程为(单一事务):

  • 1、利用所配置的PlatformTransactionManager事务管理器新建一个数据库连接conn

这个连接保存在一个ThreadLocal<Map<DateSource,conn>>,key是数据源,value就是连接coon

  • 2、修改数据库连接的autocommit为false,并设置@Transactional其他属性配置

  • 3、执行MethodInvocation.proceed()方法,简单理解就是执行业务方法,其中就会执行sql

  • 4、如果没有抛异常,则提交

  • 5、如果抛了异常,则回滚

注意:这里不能由JDBCTemplate或Mybatis自己建立连接,否则就无法用到事务了,spring处理多事务也是通过建立不同连接来实现的。

3、事务传播机制

3.1、入门案例

先来看一种情况,a()在一个事务中执行,调用b()方法时需要新开一个事务执行的情况分析:

  • 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a

  • 将数据库连接a的autocommit改为false,并设置其他属性

  • 把数据库连接a设置到ThreadLocal中

  • 执行a()方法中的sql

  • 执行a()方法过程中,调用了b()方法,需要新开一个事务(注意用代理对象调用b()方法)

这里有一个AOP的知识点:如果不用代理对象调,在类中调的话,则是调用的普通对象的b()方法,这是不会走代理逻辑的,所以也就用不到事务。

  • 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线程其实已经拥有一个Spring事务了,则进行挂起

  • 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象中

  • 挂起完成后,再次利用事务管理器新建一个数据库连接b,将数据库连接b的autocommit改为false

  • 把数据库连接b设置到ThreadLocal中,执行b()方法中的sql

  • b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交

  • 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到ThreadLocal中

  • a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交

这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。

3.2、七大事务传播属性

  • PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

  • PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。

  • PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

备注:常用的两个事务传播属性是1和4,即PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW

其中,以非事务方式运行,表示以非Spring事务运行,表示在执行这个方法时,Spring事务管理器不会去建立数据库连接,执行sql时,由Mybatis或JdbcTemplate自己来建立数据库连接来执行sql。

3.3、案例分析

(1)情况1

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
  }
}

默认情况下传播机制为REQUIRED,表示当前如果没有事务则新建一个事务,如果有事务则在当前事务中执行。

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 执行conn的commit()方法进行提交

(2)情况2

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
        int result = 100/0;
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

(3)情况3

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional
  public void a() {
    // a方法中的sql
        int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

(4)情况4

@Component
public class UserService {
  @Autowired
  private UserService userService;
 
  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }
 
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void a() {
    // a方法中的sql
    int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn

  • 设置conn的autocommit为false

  • 执行test方法中的sql

  • 又新建一个数据库连接conn2

  • 执行a方法中的sql

  • 抛出异常

  • 执行conn2的rollback()方法进行回滚

  • 继续抛异常,对于test()方法而言,它会接收到一个异常,然后抛出

  • 执行conn的rollback()方法进行回滚,最终还是两个方法中的sql都回滚了

3.4、事务失效的场景

(1)@Transactional作用在非public修饰的方法上

如果Transactional注解应用在非 public 修饰的方法上,Transactional将会失效。

是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法 或 JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息

(2)@Transactional作用在使用final或者static修饰的方法

Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。

根据这个原理可知,如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。

(3)@Transactional 注解属性propagation设置错误

传播属性设置为非事务方式。

TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

(4)同一个类中方法调用导致@Transactional失效

这里有一个AOP的知识点:如果不用代理对象调,在类中调的话,则是调用的普通对象的b()方法,这是不会走代理逻辑的,所以也就用不到事务。

(5)多线程情况

情况一:父线程抛异常,子线程OK

父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚

情况二:父线程OK,子线程抛异常

由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。

 

参考文章:https://blog.csdn.net/u012060033/article/details/87911330

标签:事务,spring,Transactional,sql,原理,执行,方法,数据库
From: https://www.cnblogs.com/jing-yi/p/18199933

相关文章

  • Spring 面向切面编程AOP 详细讲解
    1.Spring面向切面编程AOP详细讲解@目录1.Spring面向切面编程AOP详细讲解每博一文案2.AOP介绍说明2.1AOP的七大术语2.2AOP当中的切点表达式3.使用Spring对AOP的实现使用3.1准备工作3.2Spring基于AspectJ的AOP注解式开发3.2.1实现步骤3.2.2各个通知类型的说明3......
  • MySQL面试必备三之事务
    本文首发于公众号:Hunter后端原文链接:MySQL面试必备三之事务这一篇笔记介绍一下MySQL的事务,面试中常被问到关于事务的几个问题如下:事务是什么为什么需要事务,事务有什么作用事务的特点事务可能带来哪些问题事务有哪些隔离级别,这些隔离级别都可以解决哪些问题可重复读隔......
  • net core jwt的基本原理和实现
    NETCore中的JWT(JSONWebToken)基本原理和实现主要涉及到身份验证和授权的过程。以下是对JWT原理和实现的简要概述:JWT原理JWT是一种开放标准(RFC7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。这些信息可以被验证和信任,因为它们是数......
  • spring boot整合shiro
    导入依赖<!--Subject用户SecurityManager管理所有用户Realm连接数据库--><!--shiro整合spring的包--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version......
  • 在spring boot中使用redis
    第一步,引入依赖```<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>```第二步,配置redisspring:redis:host:loc......
  • 64-SpringBoot源码分析
    Starter是什么?我们如何使用这些Starter?为什么包扫描只会扫描核心启动类所在的包及其子包?在SpringBoot启动过程中,是如何完成自动配置的?内嵌Tomcat是如何创建并启动的?引入了web场景对应的Starter,SpringMVC是如何完成自动装配的?1.源码环境构建https://gith......
  • 45.Spring(IOC)学习整理
    既然想要理解原理那尽头即是语文IOC控制反转先说一下大致笼统的:由用户管理Bean转变为框架管理Bean框架即是IOCContainerBean就相当于一个组件调用它实现相应功能Ioc即“控制反转”,不是什么技术,而是一种设计思想控制不再进行new创建对象而是IOC容器控制对象的......
  • 汇编语言基础及编译原理(网安)
    汇编语言基础及编译原理二进制基础程序的编译汇编与链接从c语言到可执行程序源代码.c编译汇编代码.s汇编目标文件.o链接(静态库直接拷贝,动态库运行时通过动态链接方式加载)可执行文件(p)x86机器指令入门栈一种先进后出的数据结构被用于保存函数的局部(保存局部变量和......
  • SkyWalking 单机安装 + 集成springboot
    一、下载地址https://skywalking.apache.org/downloads/需下载安装包:SkyWalkingAPM(v9.6.0)(新版本BoosterUI已集成在SkyWalkingAPM中)apache-skywalking-apm-9.6.0.tarJavaAgent(v9.1.0)apache-skywalking-java-agent-9.1.0.tar二、安装2.1、安装SkyWalk......
  • cpp 程序设计原理和实践(1)
    初始化的时候避免窄化doublex{2.4};如何将一个大任务分割为多个小任务基本原则抽象:不需要了解程序的具体实现细节,细节隐藏在相应的接口中。分治:将一个大问题分割为多个小问题。如何进行分割明确各个子程序之间的关系--按照功能进行划分是最简单的编程之前先进......