【工作中问题解决实践 九】Spring中事务传播的问题排查

最近在工作中遇到了三个关于事务操作的问题,顺便就着这三个问题又回顾了一遍Spring的事务相关的操作,想着一次性把这个问题研究明白了,后续使用事务的时候也能踏实点,让事务发挥真实的作用

什么是事务?什么是事务管理?什么是Spring事务

什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错。

什么是事务管理,事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不受损失

关于事务的基本概念和定义可以参照我的另一篇Blog:【Spring学习笔记 九】Spring声明式事务管理实现机制。Sping事务简而言之就是一种JTA事务,这里不再详细展开。

一个用来演示的例子

我们还是沿用:【Spring学习笔记 九】Spring声明式事务管理实现机制这篇文章中的例子,只不过为了更贴近工作实战,这里我重构了一下代码实现。

单元测试入口

package com.example.springboot;import com.example.springboot.model.Person;
import com.example.springboot.service.PersonAggService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.addPerson(person, 100086L);}
}

聚合的Service方法

package com.example.springboot.service;import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonAggService {@Resourceprivate PersonService personService;@Resourceprivate PersonMaintainService personMaintainService;public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);//保存人员创建者personMaintainService.savePersonCreator(creatorId);}
}

数据服务方法

package com.example.springboot.service;import com.example.springboot.dao.PersonDao;
import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}public void insert(Person person) {personDao.insert(person);}}

人员维护人添加方法

package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

数据表落库

在这里插入图片描述

不使用事务的情况

不使用事务的情况虽然单元测试报错了
在这里插入图片描述

但是数据库落库还是成功了:
在这里插入图片描述

遇到的三个事务问题

依据以上的基本case示例,模拟我遇到的两个问题和解决方案

问题一:Transaction rolled back because it has been marked as rollback-only

为了保证整体数据与预期一致可以回滚,我使用了事务,首先在外层加事务:

   @Transactional(rollbackFor = Exception.class)public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);try {//发送人员同步到下游系统personMaintainService.savePersonCreator(creatorId);} catch (Exception e) {System.out.println("保存人员维护人异常但是被catch住了");}}

同时呢人员创建人这块我认为这里不需要报错阻塞整体操作,如果这里有问题只要有日志记录就行了,我通过巡检检查关注到即可,所以对这块代码加了try catch,但是呢因为内部代码不知道是谁写的也加了事务,

@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

因为它们用的都是默认的传播机制,所以可以看做一个事务,使用REQUIRED传播模式,addAndSendPerson和savePersonCreator在同一个事务里面,savePersonCreator抛出异常要回滚,addAndSendPerson try Catch了异常正常执行commit,同一个事务一个要回滚,一个要提交,会报read-only异常,结果就是全部回滚,而外层所以这里就会出现rollback-only
在这里插入图片描述
解决方法有两种,一种是

干掉内层事务

内层的savePersonCreator事务干掉,这时数据也能落库成功了,事实上因为JTA的事务是有非常强的业务含义的,所以对于DAO层或简单的数据操作指令,不要加事务,否则对于较长的外部调用链路,会在传播过程中导致意外情况发生
在这里插入图片描述

内层声明为新事务

还有一种解决思路就是内层的事务声明为新事务

package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

声明后再跑单测:
在这里插入图片描述
数据也落库成功了,因为是两个独立事务,所以内层事务遇到异常回滚,外层事务捕获到了异常catch住了,没有继续回滚

问题二:事务设置为什么不生效?

还有个例子是方法设置了事务但是不生效,我们再调整下以上的代码,模拟一种场景:savePerson要执行很多事项,但是不希望saveDate的执行异常回滚影响整体回滚,所以saveDate中的核心数据操作被try catch,并且声明内部的savePersonCreator方法为新事务,符合上边我们提到的那种场景,这种情况下理论上savePersonCreator抛出异常后会使 personDao.insert(person);回滚,数据不能写入

@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}}
@Service
public class PersonAggService {@ResourcePersonDao personDao;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");try {savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println("捕获到创建人员异常");}}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

但事实上,数据库写入数据能成功:
在这里插入图片描述
数据库数据写入成功了
在这里插入图片描述
这是因为:Spring中事务的默认实现使用的是AOP,也就是代理的方式,如果大家在使用代码测试时,同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用,所以实际上saveDate和savePersonCreator的事务都没有生效

把需要成为事务的方法单独抽出来

上述代码我们把需要有事务机制的savePersonCreator单独抽到一个方法中

@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}
}
@Service
public class PersonAggService {@ResourcePersonService personService;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");try {personService.savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println("捕获到创建人员异常");}}
}
@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

这样savePersonCreator的事务就生效了,数据没有插入成功

问题三:为什么会发生org.springframework.dao.CannotAcquireLockException

经过上一步的调整事务总算生效了,这里我们对上述代码再做一个调整,让内层和外层的事务都对同一张表进行操作,代码清单如下:

@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}}
@Service
public class PersonAggService {@ResourcePersonService personService;@ResourcePersonDao personDao;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");Person personOut = new Person();personOut.setUsername("outPerson");personOut.setAge(30);personOut.setEmail("111@qq.com");personOut.setPassword("111111");personOut.setPhone(11111111);personOut.setHobby("跳远");personDao.insert(personOut);try {personService.savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println("捕获到创建人员异常");}}}
@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}

我们发现外部事务的数据提交成功,而内部事务的异常被捕获了
在这里插入图片描述
同时这句话也没有打印出来,说明还没有执行到手动抛异常的位置:
在这里插入图片描述

外部事务数据正确落库
在这里插入图片描述

为了更细致的看,我们把异常捕获干掉

@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}}
@Service
public class PersonAggService {@ResourcePersonService personService;@ResourcePersonDao personDao;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");Person personOut = new Person();personOut.setUsername("outPerson");personOut.setAge(30);personOut.setEmail("111111@qq.com");personOut.setPassword("111111");personOut.setPhone(11111111);personOut.setHobby("跳远");personDao.insert(personOut);personService.savePersonCreator(person, creatorId);}
}
@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);}
}

在这里插入图片描述
其原因就是两个不同的事务,对同一张表进行操作:

  • 事务的传播行为: Spring 允许定义事务的传播行为,即在一个事务方法中调用另一个事务方法时如何处理事务。其中一种传播行为是 Propagation.REQUIRES_NEW,它表示每次方法被调用时都会启动一个新的事务,而不管是否已经存在一个事务。如果在嵌套事务中使用 Propagation.REQUIRES_NEW,可能会导致内层事务与外层事务并发执行,从而产生并发问题。于是抛出了获取锁失败的异常。为了方便大家看是不是一个问题,把这个异常详细粘贴出来

而多次调用实验也表明,有时候数据是会落库成功的,所以就看内外事务的并发执行时机了。

org.springframework.dao.CannotAcquireLockException: 
### Error querying database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may exist in file [F:\JavaWeb\springboot\target\classes\mapper\personMapper.xml]
### The error may involve com.example.springboot.dao.PersonDao.insert-Inline
### The error occurred while setting parameters
### SQL: insert into person (id,username,password,age,phone,email,hobby) values (?,?,?,?,?,?,?)
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transactionat org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:267)at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91)at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)at com.sun.proxy.$Proxy107.selectOne(Unknown Source)at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:160)at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)at com.sun.proxy.$Proxy108.insert(Unknown Source)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)at com.sun.proxy.$Proxy109.insert(Unknown Source)at com.example.springboot.service.PersonService.savePersonCreator(PersonService.java:27)at com.example.springboot.service.PersonService$$FastClassBySpringCGLIB$$2df9c1a9.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)at com.example.springboot.service.PersonService$$EnhancerBySpringCGLIB$$7b8fd37c.savePersonCreator(<generated>)at com.example.springboot.service.PersonAggService.savePerson(PersonAggService.java:32)at com.example.springboot.service.PersonAggService$$FastClassBySpringCGLIB$$7eb0a300.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)at com.example.springboot.service.PersonAggService$$EnhancerBySpringCGLIB$$ec6485e1.savePerson(<generated>)at com.example.springboot.SpringbootApplicationTests.springTransTest(SpringbootApplicationTests.java:24)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)at java.util.ArrayList.forEach(ArrayList.java:1257)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)at java.util.ArrayList.forEach(ArrayList.java:1257)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transactionat com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123)at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370)at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3461)at com.alibaba.druid.wall.WallFilter.preparedStatement_execute(WallFilter.java:663)at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3459)at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3459)at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:167)at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:497)at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:64)at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)

内部不声明新事务

解决方案一就是内部不声明新事务,同一个事务下就不存在这个问题了

@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}@Transactional(rollbackFor = Exception.class)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);}
}

在运行一下发现两条记录都插入成功了
在这里插入图片描述

内部事务抽出来

还有一种方式就是不要搞事务嵌套,把内部方法抽出来和外层的平行,内部方法执行依赖的入参由原外部事务的返回值给出,做到不相互依赖。

问题场景

基于保密原则,原始代码不能贴出来,所以用一些示例表示,其实真实场景比较复杂,我们正在做的事情是数据迁移,一个加盟商可能带一堆门店迁移,它们都有公司的数据,并且我们希望一个门店迁移失败不影响整体【加盟商是外层,门店是内层】

  1. 所以我开始在加盟商和门店的同步方法都加了事务(默认传播机制),然后catch门店抛出的异常,于是就导致了问题一:Transaction rolled back because it has been marked as rollback-only
  2. 为了解决问题一,我将门店的同步方法声明为新事务,结果发现事务不生效,原来是我解决问题二的时候把门店方法放回到了加盟商同步方法一个类,导致注解不生效
  3. 把门店方法又挪出来后又发现数据同步时会偶发org.springframework.dao.CannotAcquireLockException,发现是加盟商和门店都对公司表有插入行为,产生了并发死锁。于是我就把门店的同步单独提出来和加盟商同步平级按顺序执行,门店需要的参数由加盟商执行结果返回,同时每个门店执行加了catch,只影响自己

这样总算解决了问题,表面看好像很简单,但经历了一段时间的排查过程,总结一句话:事务是一系列紧密相关行为的集合。按这个定义其实我早就应该想到把门店的同步抽出来,不要搞什么嵌套事务。不过怎么说排查过程中也加深了对Spring事务的理解吧

Spring事务的更多传播机制

以上两个示例是真实工作中遇到的,基于安全原则模拟了两个类似的case,其实spring还有更多的花式的事务使用机制,可以参照带你读懂Spring 事务——事务的传播机制

总结一下

照例总结一下,在单一的数据操作方法不要加事务,事务应该是一系列操作指令的聚合,添加了细粒度的事务可能会导致上层使用者在方法添加事务时产生了非预期的传播机制。当然如果内外层的方法调用都很复杂,则基于自己的预期进行考虑,如果不希望内层方法影响外层方法,可以使用外层方法异常捕获加内层事务的REQUIRES_NEW传播机制解决。需要注意的是Spring的事务是基于AOP实现的,所以对象内部方法调用,不会通过Spring代理,也就是事务不会起作用,这点非常重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/33018.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Linux从练气到飞升》No.12 Linux进程概念

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

算法与数据结构(二十二)动态规划解题套路框架

动态规划解题套路框架 此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 动态规划问题&#xff08;Dynamic Programming&#xff09;应该是很多读者头疼的&#xff0c;不过这类问题也是最具有技巧性&#xff0c…

Arcgis将一个shp依照属性表导出为多个shp

# -*- coding:utf-8 -*-import arcpy import osfrom arcpy import env#env.workspace "./" #自己设置路径shp rC:\Users\Administrator\Desktop\Lake\xxx.shp #shp文件路径outpath r"C:\Users\Administrator\Desktop\Lake\fenli" #输出结果路径with arc…

使用Python和wxPython将图片转换为草图

导语: 将照片转换为艺术风格的草图是一种有趣的方式&#xff0c;可以为您的图像添加独特的效果。在本文中&#xff0c;我们将介绍如何使用Python编程语言和wxPython图形用户界面库来实现这一目标。我们将探讨如何使用OpenCV库将图像转换为草图&#xff0c;并使用wxPython创建一…

在线高精地图生成算法调研

1.HDMapNet 整体的网络架构如图所示&#xff0c;最终的Decoder输出三个分支&#xff0c;一个语义分割&#xff0c;一个embedding嵌入分支&#xff0c;一个方向预测。然后通过后处理将这些信息处理成向量化的道路表示。 img2bev的方式之前有IPM&#xff0c;通过假设地面的高度都…

面试热题(三数之和)

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 输入&…

[保研/考研机试] KY35 最简真分数 北京大学复试上机题 C++实现

题目链接&#xff1a; 最简真分数https://www.nowcoder.com/share/jump/437195121691719749588 描述 给出n个正整数&#xff0c;任取两个数分别作为分子和分母组成最简真分数&#xff0c;编程求共有几个这样的组合。 输入描述&#xff1a; 每组包含n&#xff08;n<600&…

微信小程序实现当前页面更新上一个页面

日常项目中需要实现的一个价格脱敏功能&#xff1a;通过点击页面二中的查看完整信息 点击回退按钮实现页面一中的价格显露出来 通过查询了大量资料发现 大多数都是通过调用上一个接口的onload 或者onshow 实现视图更新 经测试后 发现 无法实现 只能更改数据 无法更新视图 实现…

算能BM1684X部署手写数字识别模型

大致流程--------------------------------------------------------------- 参考《sophon-sail_zh》 移植步骤------------------------------------------------------------------------ 首先搭建好自己的网络模型&#xff0c;并导出为onnx格式--具体可以参照--> GitH…

大语言模型之一 Attention is all you need ---Transformer

大语言模型已经在很多领域大显身手&#xff0c;其应用包括只能写作、音乐创作、知识问答、聊天、客服、广告文案、论文、新闻、小说创作、润色、会议/文章摘要等等领域。在商业上模型即产品、服务即产品、插件即产品&#xff0c;任何形态的用户可触及的都可以是产品&#xff0c…

opencv基础46-图像金字塔02-拉普拉斯金字塔

前面我们介绍了高斯金字塔&#xff0c;高斯金字塔是通过对一幅图像一系列的向下采样所产生的。有时&#xff0c;我们希望通过对金字塔中的小图像进行向上采样以获取完整的大尺寸高分辨率图像&#xff0c;这时就需要用到拉普拉斯金字塔 前面我们已经介绍过&#xff0c;一幅图像在…

.Net C# 免费PDF合成软件

最近用到pdf合成&#xff0c;发现各种软件均收费啊&#xff0c;这个技术非常简单&#xff0c;别人写好的库一大把&#xff0c;这里用到了PDFsharp&#xff0c;项目地址Home of PDFsharp and MigraDoc Foundation 软件下载地址 https://download.csdn.net/download/g313105910…

网站SSL安全证书是什么及其重要性

网站SSL安全证书具体来说是一个数字文件&#xff0c;是由受信任的数字证书颁发机构&#xff08;CA机构&#xff09;进行审核颁发的&#xff0c;其中包含CA发布的信息&#xff0c;该信息表明该网站已使用加密连接进行了安全保护。 网站SSL安全证书也被称为SSL证书、https证书和…

Similarities:精准相似度计算与语义匹配搜索工具包,多维度实现多种算法,覆盖文本、图像等领域,支持文搜、图搜文、图搜图匹配搜索

Similarities&#xff1a;精准相似度计算与语义匹配搜索工具包&#xff0c;多维度实现多种算法&#xff0c;覆盖文本、图像等领域&#xff0c;支持文搜、图搜文、图搜图匹配搜索 Similarities 相似度计算、语义匹配搜索工具包&#xff0c;实现了多种相似度计算、匹配搜索算法&…

IDEA之Debug调试

资料来源于韩老师视频 &#xff08;一&#xff09;初探debug 1、打断点的话&#xff1a;直接在该行前面单击左键&#xff0c;出现小红点就是断点了。 想要取消断点的话&#xff0c;再单击小红点即可。 运行debug时&#xff0c;右键选择"Debug…"而不是选“Run…”…

【C++】开源:spdlog跨平台日志库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍spdlog日志库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下…

​LeetCode解法汇总1572. 矩阵对角线元素的和

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 给你一个正…

配置vscode

配置vscode 设置相关 网址&#xff1a;https://code.visualstudio.com/ 搜索不要用百度用这个&#xff1a;cn.bing.com 1.安装中文包 Chinese (Simplified) (简体中文) 2.安装 open in browser 3.安装主题 Atom One Dark Theme 4. 安装图标样式 VSCode Great Icons 5.安装 L…

yum 安装本地包 rpm

有时直接yum install 有几个包死活下不下来 根据网址&#xff0c;手动下载&#xff0c;下载后上传至 centos 然后运行 sudo yum localinstall xxx.rpm 即可安装 参考 https://blog.csdn.net/weiguang1017/article/details/52293244

MachineLearningWu_16/P72-P77_Diagnostic

x.1 导数&#xff0c;计算图&#xff0c;大型网络 计算图就是根据链式法则求取偏导&#xff0c;大型网络就是多层网络堆叠而成。 x.2 Diagnostic 在我们对深度学习有了一些认知后&#xff0c;最重要的就是模型的诊断&#xff0c;以带有L1正则化的线性回归为例&#xff0c;我…