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

这篇具有很好参考价值的文章主要介绍了【工作中问题解决实践 九】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 {
    @Resource
    private PersonAggService personAggService;

    @Test
    public 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 {
   
    @Resource
    private PersonService personService;
    @Resource
    private 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 {
    @Resource
    PersonDao 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();
    }
}

数据表落库

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

不使用事务的情况

不使用事务的情况虽然单元测试报错了
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

但是数据库落库还是成功了:
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

遇到的三个事务问题

依据以上的基本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
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java
解决方法有两种,一种是

干掉内层事务

内层的savePersonCreator事务干掉,这时数据也能落库成功了,事实上因为JTA的事务是有非常强的业务含义的,所以对于DAO层或简单的数据操作指令,不要加事务,否则对于较长的外部调用链路,会在传播过程中导致意外情况发生
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

内层声明为新事务

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

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();
    }
}

声明后再跑单测:
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java
数据也落库成功了,因为是两个独立事务,所以内层事务遇到异常回滚,外层事务捕获到了异常catch住了,没有继续回滚

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

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

@SpringBootTest
class SpringbootApplicationTests {
    @Resource
    private PersonAggService personAggService;

    @Test
    public 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 {
    @Resource
    PersonDao 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中事务传播的问题排查,工作中问题解决实践,spring,java
数据库数据写入成功了
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java
这是因为:Spring中事务的默认实现使用的是AOP,也就是代理的方式,如果大家在使用代码测试时,同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用,所以实际上saveDate和savePersonCreator的事务都没有生效

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

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

@SpringBootTest
class SpringbootApplicationTests {
    @Resource
    private PersonAggService personAggService;

    @Test
    public 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 {
    @Resource
    PersonService 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 {
    @Resource
    PersonDao 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 {
    @Resource
    private PersonAggService personAggService;

    @Test
    public 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 {
    @Resource
    PersonService personService;
    @Resource
    PersonDao 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 {
    @Resource
    PersonDao 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();
    }
}

我们发现外部事务的数据提交成功,而内部事务的异常被捕获了
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java
同时这句话也没有打印出来,说明还没有执行到手动抛异常的位置:
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

外部事务数据正确落库
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

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

@SpringBootTest
class SpringbootApplicationTests {
    @Resource
    private PersonAggService personAggService;

    @Test
    public 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 {
    @Resource
    PersonService personService;
    @Resource
    PersonDao 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 {
    @Resource
    PersonDao 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中事务传播的问题排查,工作中问题解决实践,spring,java
其原因就是两个不同的事务,对同一张表进行操作:

  • 事务的传播行为: 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 transaction

	at 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 transaction
	at 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 {
    @Resource
    PersonDao 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);
    }
}

在运行一下发现两条记录都插入成功了
【工作中问题解决实践 九】Spring中事务传播的问题排查,工作中问题解决实践,spring,java

内部事务抽出来

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

问题场景

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

  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代理,也就是事务不会起作用,这点非常重要。文章来源地址https://www.toymoban.com/news/detail-631774.html

到了这里,关于【工作中问题解决实践 九】Spring中事务传播的问题排查的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring事务和事务传播机制(1)

    ❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️ Spring + Spring MVC + MyBatis_冷兮雪的博客-CSDN博客 在Spring框架中,事务管理是一种用于维护数据库操作的一致性和完整性的机制。Spring事务管理提供了灵活的方式来处理事务,包括事务的创建

    2024年02月12日
    浏览(27)
  • Spring—事务及事务的传播机制

    将一组操作封装成一个执行单元, 即这一组操作一同成功 / 一同失败 举个栗子🌰 未使用事务 滑稽老哥给女神转账 520 由于某种原因, 女神并未收到转账的 520, 而滑稽老哥却被扣款 520 使用事务 滑稽老哥给女神转账 520 由于某种原因, 女神并未收到转账的 520 因为使用事务, 所以

    2024年02月13日
    浏览(37)
  • 【Spring】深入理解 Spring 事务及其传播机制

    在 Spring 框架中,事务(Transaction)是一种用于管理数据库操作的机制,旨在 确保数据的 一致性、可靠性和完整性 。事务可以将一组数据库操作(如插入、更新、删除等)视为一个单独的执行单元,要么 全部成功地执行,要么全部回滚 。这样可以确保数据库在任何时候都保

    2024年02月12日
    浏览(51)
  • Spring Boot 事务和事务传播机制

    事务定义 将一组操作封装成一个执行单元 (封装到一起),这一组的执行具备原子性, 那么就要么全部成功,要么全部失败. 为什么要用事务? 比如转账分为两个操作: 第一步操作:A 账户-100 元。 第二步操作:B账户 +100 元。 如果没有事务,第一步执行成功了,第二步执行失败了,

    2024年02月11日
    浏览(29)
  • Spring事务传播机制

    编程式事务管理:通过  TransactionTemplate 或者 TransactionManager 手动管理事务,实际应用中很少使用,这不是本文的重点,就不在这里赘述。 声明式事务管理:使用场景最多,也是最推荐使用的方式,直接加上@Transactional注解即可。 @Transactional 注解是用于声明事务性方法的注解

    2024年01月16日
    浏览(28)
  • Spring事务传播机制解析

    在Java的Spring框架中,事务管理是保证应用数据一致性和可靠性的关键。Spring提供了灵活的事务传播机制,它定义了事务边界,以及在嵌套方法调用时如何处理事务。本文旨在深入探讨Spring的事务传播行为,帮助开发者更好地理解和运用这一重要特性。 事务传播机制指的是在

    2024年01月16日
    浏览(29)
  • spring的事务传播机制

    嫌弃内容代码复杂的可直接看思维导图大纲即可 指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行 默认,当前存在事务,则加入该事务;不存在事务,创建新事务。 始终以新的事务运行,当前存在事务,则挂起原事务;不存在事务,创建新事务

    2023年04月23日
    浏览(44)
  • Spring——Spring事务的实现方式及传播特性

    Spring事务的特性及隔离级别同事务,详情请见 事务——什么是事务,事务的特性,事务的隔离级别_醉酒的戈多的博客-CSDN博客 在使用Spring框架的时候,有以下两种事务的实现方式: 编程式事务:用户自己通过代码来控制事务的处理逻辑 声明式事务:通过@Transactional注解来实

    2024年02月13日
    浏览(26)
  • 【JavaEE进阶】Spring事务和事务传播机制

    Spring 事务是 Spring 框架提供的一种机制,用于 管理数据库操作或其他资源的一组相关操作 ,以确保它们在一个原子、一致、可靠和隔离的执行单元内进行。事务用于维护数据的完整性并支持并发访问数据库时的数据一致性。 Spring 事务的主要特点包括: 原子性(Atomicity):

    2024年02月09日
    浏览(54)
  • 一文详解Spring事务传播机制

    目录 背景 Spring事务 @Transactional注解 使用场景 失效场景 原理 常用参数 注意 事务传播机制 处理嵌套事务流程 主事务为REQUIRED子事务为REQUIRED 主事务为REQUIRED子事务为REQUIRES_NEW 主事务为REQUIRED子事务为NESTED 实现方式 源码解析 我们在使用Spring管理数据库事务的时候很方便,只

    2023年04月26日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包