spring-- 事务失效原因及多线程事务失效解决方案
事务失效原因
-
类的自调用:直接调用本类的方法,没有通过代理对象来调用方法,代理对象内部的事务拦截器不会拦截到这次行为。则不可能开启事务
-
使用私有方法:因为spring的事务管理是基于AOP实现的,AOP代理无法拦截目标对象内部的私有方法调用。直接没有代理对象
-
使用多线程:在主线程中开启的事务不会自动传播到其创建并执行的子线程中,则会失效
-
如果事务回滚,将报错代码用try catch来进行捕获,事务就不会失效
-
其他比如定义事务属性传播性时定义的是REQUIRES_NEW(脱离当前事务),事务也不会受到影响
声明式事务的底层原理:
-
当目标类被spring管理时(即@Component,@Service,@controller,@Repository注解时),spring会为目标对象创建一个代理对象。代理对象负责拦截目标方法的调用,并在必要时应用事务管理(AOP思想)
-
代理对象内部包含一个事务拦截器TransactionInterceptor,负责处理事务相关的逻辑
-
事务拦截器会检查方法上是否添加了@Transactional注解,来决定是否应用事务(JDBC的connection调用setAutoCommit(false),然后会将connection存到ThreadLocal(本地线程)中,
(为了嵌套方法可以拿到外层事务的connection对象,只有使用同一个connection对象才能保证使用同一个事物)
然后去执行方法,从而执行数据库操作,然后再执行嵌套方法,然后再嵌套方法的代理对象,就可以拿到外层事务的Thread Local中connection对象,从而执行方法,嵌套方法发现ThreadLocal中有值,他不会提交事务,而是统一的交给外层事务进行提交)
-
事务拦截器在目标方法执行前后应用事务通知,(前置通知和后置通知)在方法执行前,事务拦截器启动事务;在方法执行后,根据方法的执行结果决定事务的提交或回滚
如下图所示:
多线程事务失效原因
如果在方法中开启新线程去调用嵌套方法时,这时嵌套线程就拿不到外层事务的ThreadLocal中的connection对象。
因为ThreadLocal是绑定在主线程上的。
所以在新的线程中去调用嵌套方法时,就拿不到外层事务的connection对象,
然后就会自己新建一个事务,自己存一个connection到自己的ThreadLocal中,
这样的话,方法和嵌套方法就各开启了一个事务,且是同级事务,相互不影响。
但是这样在外层事务失败回滚的时候,内层事务不受外层事务的影响,不进行回滚的话,就会造成事务回滚但还是写入数据库中的现象,我们要保证数据库数据的一致性!
解决方法
在创建一个异步线程之后,可以手动的往嵌套方法的Tread Local中将外层事务connection对象存入,那么嵌套方法就可以从自己的Thread Local中拿到外层事务的connection了,这样方法和嵌套方法用的就是同一个事务了。这样就不会出现多线程事务失效的情况了
前置知识:
编程式事务中DataSourceTransactionManager类org.springframework.jdbc.datasource.DataSourceTransactionManager中的doBegin方法就是spring底层在开启事务的时候进行调用的,在方法中获取数据库连接,并开启事务
所以如果想要获取对应的connection,需要调用事务同步管理器的getResource()方法,
然后将spring容器当中的datasource传进来,就可以拿到,
然后再调用BindResource方法就可以将异步线程中的ThreadLocal中存入外层事务的connection
解决方案代码展示:
private DataSource datasource;ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizedManager.getResource(dataSource);new Thread(()->{//绑定主线程的connection到子线程中TransactionSynchronizedManager.bindResource(dataSource,connectionHolder);//调用方法})
这样便可以解决spring事务失效的情况,希望对大家有所帮助!