spring事务原理
事务,就是一组操作数据库的动作集合,要么全部成功,要么全部失败。
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
2020-05-18 Redis高级特性