贴一篇源码分析的好文章:https://blog.csdn.net/qq_30905661/article/details/114400417
本质:
一个事务对应一个数据库连接。
通过 this 来调用某个带有 @Transactional 注解的方法时,这个注解是失效的,可以看做这个方法(如上图B)上没有这个注解,当然书写的传播机制限制也是无效的,例如:propagation = Propagation.MANDATORY、propagation = Propagation.NEVER。
但是若调用A的是CGLIB生成的代理对象,并且A上有 @Transactional 注解,那么方法A是具有事务的,方法B中的sql 就在方法A的事务中执行,所以整体A,B是有事务的。
Spring的事务是如何实现的?
- spring事务底层是通过数据库事务和AOP实现的
- 首先对于使用@Transactional的注解的bean,spring会创建一个代理对象作为bean
- 当调用代理对象的方法时,spring会判断该方法上是否加了@Transactional注解
- 如果加了,就会利用事务管理器创建一个数据库连接,并修改数据库连接的 autocommit 为 false,禁止自动提交
- 然后执行该方法,若方法没有抛异常则会提交事务,反之亦然
- spring事物的隔离级别就是对应数据库的隔离级别
- spring事务的传播机制是spring自己实现的,是spring事务中最复杂的
- spring事物的传播机制是基于数据库连接来做的,一个连接一个事务,传播事务实际上是开了一个新的数据库连接,在此基础上执行sql
Spring事物的传播机制?
spring事务默认是注解是 REQUIRED,支持事务的传播,使用同一个数据库连接。
REQUIRED:spring默认的事务传播机制,A存在事务,则B加入A的事务;A没有事务则会新建一个数据库事务;
SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行
MANDATORY:(强制性使用第一个事务)A存在事务,则B加入A的事务;A没有事务,则抛异常
REQUIRES_NEW:创建一个新事务,B在这个新事务中执行;A如果有事务将会被挂起,等待B事务方法执行结束(commit or rollback),当B事务执行结束后,A事务被唤醒继续执行,若B抛出了异常给A 或 A 方法执行出了异常,那么在 A 事务中执行的 sql 将会被回滚,B 事务中的sql 由B的事务管理器控制,A、B中的sql不在同一数据库连接中执行,即内层事务B已经 commit 或 rollback, 外层事务干扰不了。
NOT_SUPPORTED:(不支持事务),若A存在事务,则挂起A的事务,以非事务方式运行
NEVER:(不支持事务),若A存在事务抛异常
NESTED:A存在事务,则在嵌套事务中执行;不存在则和 REQUIRED 一样开启一个新事务
那些情况会导致Spring事务的失效?失效的原因是?
-
数据库不支持事务
-
类没有被spring管理(ioc),没有加注解。
-
未启用Spring事务管理功能(@EnableTransactionManagement)
-
数据源没有配置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); }
-
没有加@Configuration注解:springboot基本没有这个问题;Spring可能会出现这个问题,原因是由于mybatis或JdbcTemplate会从ThreadLocal中获取数据库连接,但是ThreadLocal底层引用的是ThreadLocalMap,Map的key是一个DataSource对象,value是数据库连接。如果没有加@Configuration注解的话,会导致Map中的DataSource对象和mybatis、jdbcTenplate中的DataSource对象不相等,所有就拿不到数据库连接,以至于自己去创建连接了。
-
异常被吃掉:默认情况下Spring会捕获 error 和 RunTimeException ,spring捕获不到异常也就不会回滚了,例如 try-catch
-
方法是private的:spring事务基于CGLIB来进行AOP,CGLIB是基于父子类来实现,子类是代理类,子类无法重写父类的private方法,也就没有办法增加spring事务逻辑。
-
方法是 final 修饰的,和private原因一致,子类不能重写增强。
-
调用A方法和B方法不是同一个线程,不同的线程拿到的数据库连接不一样。TransactionSynchronizationManager.bindResource 会将线程与数据库连接绑定。
-
rollbackFor = RuntimeException.class(默认),当抛出的异常大于定义的异常,则会导致事务失效
-
方法内自调用时对象不是同一个:Spring事务是基于Aop,只有使用代理对象调用 A 方法时,注解才能生效,而在A方法中调用 B 方法时( this.B() ),并不是使用的代理对象,所以导致B的注解失效。
自身调用失效问题:
方法A 通过 this.B() 调用方法B。
本质:通过 this 来调用某个带有 @Transactional 注解的方法时,这个注解是失效的,可以看做这个方法(如上图B)上没有这个注解,当然书写的传播机制限制也是无效的,例如:propagation = Propagation.MANDATORY、propagation = Propagation.NEVER。
但是若调用A的是CGLIB生成的代理对象,并且A上有 @Transactional 注解,那么方法A是具有事务的,方法B中的sql 就在方法A的事务中执行,所以整体A,B是有事务的。
调用使用@Transactional注解的方法时,使用的是 Spring CGLIB 创建的代理对象
调用B方法的是存储在 Spring ioc容器的bean,两个不同的对象
A调用B的结论:
- 只要A加@Transactional注解,A和B在不在同一个类中,B加不加@Transactional注解,事务都是有效的,则AB在同一事务中。
- A 不加 B加,A和B同一个类中:调用A方法的是CGLIB生成的代理对象,但是A方法没有注解,所以A方法不会被拦截;this调用B,注解失效(下图)。
- A 不加 B加,A和B不在同一个类中:不在同一个类,那么调用B的就是的就是CGLIB生成的代理对象,B的事务有效,A在外围没有事务(B已经commit或rollback了,事务管理器已经把设置auto commit = false的数据库连接释放了)。