项目中Spring事务失效的场景问题排查

【项目中Spring事务失效的场景问题排查】胸怀万里世界, 放眼无限未来。这篇文章主要讲述项目中Spring事务失效的场景问题排查相关的知识,希望能为你提供帮助。


作者:幻好
问题产生场景

项目中Spring事务失效的场景问题排查

文章图片

项目业务开发中,我们想保证数据提交的原子性,会使用事务提交的方式,比较常用的是使用的 ??@Transactional?? 的方式。但是,在某些情况下,会发现事务未生效的情况,本文就来详细研究下 spring 中事务失效的原因。
注解@Transactional简介??@Transactional?? 是 spring 中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚。??@Transactional?? 注解可以帮助我们把事务开启、提交或者回滚的操作,通过 ??aop?? 的方式进行管理。
通过 ??@Transactional?? 注解就能让 spring 为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,使我们开发人员能够专注于业务层面开发。
项目中Spring事务失效的场景问题排查

文章图片

常见 Transactional 失效的场景注解标注方法修饰符为非 publicTransactional 注解标注方法修饰符为非public时,@Transactional注解将会不起作用。
项目中Spring事务失效的场景问题排查

文章图片

例如以下代码,

// 定义一个错误的@Transactional标注实现,修饰一个默认访问符的方法
@Component
public class TestServiceImpl {
@Resource
TestMapper testMapper;

@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}

// 同一个包,新建调用对象,进行访问
@Component
public class InvokcationService {
@Resource
private TestServiceImpl testService;
public void invokeInsertTestWrongModifier(){
// 调用@Transactional标注的默认访问符方法
testService.insertTestWrongModifier();
}
}

// 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
InvokcationService invokcationService;

@Test
public voidtestInvoke(){
invokcationService.invokeInsertTestWrongModifier();
}
}


以上的访问方式,导致事务没开启,因此在方法抛出异常时,数据库的插入操作将不会回滚,如果将方法改为 public 则事务将生效并正常回滚。
注意:??protected??、??private?? 修饰的方法上使用 ??@Transactional?? 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
同一类内部调用标注的方法开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用 public 还是 private 修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
示例代码如下。

// 设置一个内部调用
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;

@Transactional
public void insertTestInnerInvoke() {
// 正常public修饰符的事务方法
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}


public void testInnerInvoke(){
// 类内部调用@Transactional标注的方法。
insertTestInnerInvoke();
}
}

// 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

@Resource
TestServiceImpl testService;

/**
* 测试内部调用@Transactional标注方法
*/
@Test
public voidtestInnerInvoke(){
//测试外部调用事务方法是否正常
//testService.insertTestInnerInvoke();
//测试内部调用事务方法是否正常
testService.testInnerInvoke();
}
}


上面就是使用的测试代码,调用一个方法在类内部调用内部被@Transactional标注的事务方法,运行结果是事务不会正常开启,插入操作保存到数据库也不会进行回滚。
事务方法内部捕捉异常事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。
示例代码如下。

@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;

@Transactional
public void insertTestCatchException() {
try {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
//运行期间抛异常
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}catch (Exception e){
System.out.println("i catch exception");
}
}
}

// 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

@Resource
TestServiceImpl testService;

@Test
public void testCatchException(){
testService.insertTestCatchException();
}
}


运行测试用例发现,虽然抛出异常,但是异常被捕捉了,没有抛出到方法外 异常后面的插入操作并没有回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出 ??throw new RuntimeException()?? ,或者注解中指定抛异常类型 ??@Transactional(rollbackFor=Exception.class)??,否则会导致事务失效,数据 ??commit?? 造成数据不一致,所以有些时候 ??try catch?? 反倒会画蛇添足。
总结@Transactional 注解的看似简单易用,但如果对它的用法一知半解,还是会踩到很多坑的。在实际项目开发中,需要明白其相关用法和原理,才能更好的掌握。



    推荐阅读