spring事务失效的几种场景以及原因

前言 spring事务失效场景可能大家在很多文章都看过了,所以今天就水一篇,看大家能不能收获一些不一样的东西。直接进入主题
spring事务失效场景以及原因 1、场景一:service没有托管给spring

public class TranInvalidCaseWithoutInjectSpring {private UserService userService; public TranInvalidCaseWithoutInjectSpring(UserService userService) { this.userService = userService; }@Transactional public boolean add(User user){ boolean isSuccess = userService.save(user); int i = 1 % 0; return isSuccess; } }

@Test public void testServiceWithoutInjectSpring(){ boolean randomBoolean = new Random().nextBoolean(); TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring; if(randomBoolean){ tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class); System.out.println("service已经被spring托管"); }else{ tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService); System.out.println("service没被spring托管"); }boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user); Assert.assertTrue(isSuccess); }

失效原因: spring事务生效的前提是,service必须是一个bean对象
解决方案: 将service注入spring
2、场景二:抛出受检异常
@Service public class TranInvalidCaseByThrowCheckException {@Autowired private UserService userService; @Transactional public boolean add(User user) throws FileNotFoundException { boolean isSuccess = userService.save(user); new FileInputStream("1.txt"); return isSuccess; } }

@Test public void testThrowCheckException() throws Exception{ boolean randomBoolean = new Random().nextBoolean(); boolean isSuccess = false; TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class); if(randomBoolean){ System.out.println("配置@Transactional(rollbackFor = Exception.class)"); isSuccess = tranInvalidCaseByThrowCheckException.save(user); }else{ System.out.println("配置@Transactional"); tranInvalidCaseByThrowCheckException.add(user); }Assert.assertTrue(isSuccess); }

失效原因: spring默认只会回滚非检查异常和error异常
解决方案: 配置rollbackFor
3、场景三:业务自己捕获了异常
@Transactional public boolean add(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) {} return isSuccess; }

@Test public void testCatchExecption() throws Exception{ boolean randomBoolean = new Random().nextBoolean(); boolean isSuccess = false; TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class); if(randomBoolean){ randomBoolean = new Random().nextBoolean(); if(randomBoolean){ System.out.println("将异常原样抛出"); tranInvalidCaseByThrowCheckException.save(user); }else{ System.out.println("设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); "); tranInvalidCaseByThrowCheckException.addWithRollBack(user); } }else{ System.out.println("业务自己捕获了异常"); tranInvalidCaseByThrowCheckException.add(user); }Assert.assertTrue(isSuccess); }

失效原因: spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知
解决方案:
1、将异常原样抛出;
2、设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
4、场景四:切面顺序导致
@Service public class TranInvalidCaseWithAopSort {@Autowired private UserService userService; @Transactional public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }}

@Aspect @Component @Slf4j public class AopAspect {@Around(value = "https://www.it610.com/article/execution (* com.github.lybgeek.transcase.aopsort..*.*(..))") public Object around(ProceedingJoinPoint pjp){try { System.out.println("这是一个切面"); return pjp.proceed(); } catch (Throwable throwable) { log.error("{}",throwable); }return null; } }

失效原因: spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样
解决方案:
1、在切面中将异常原样抛出;
2、在切面中设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
5、场景五:非public方法
@Service public class TranInvalidCaseWithAccessPerm {@Autowired private UserService userService; @Transactional protected boolean save(User user){ boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }}

public class TranInvalidCaseWithAccessPermTest {public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Application.class); TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class); boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser()); System.out.println(isSuccess); } }

失效原因: spring事务默认生效的方法权限都必须为public
解决方案:
1、将方法改为public;
2、修改TansactionAttributeSource,将publicMethodsOnly改为false【这个从源码跟踪得出结论】
3、开启 AspectJ 代理模式【从spring文档得出结论】
文档如下

Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
具体步骤:
1、在pom引入aspectjrt坐标以及相应插件
org.aspectj aspectjrt 1.8.9 org.codehaus.mojo aspectj-maven-plugin 1.9 trueorg.springframework spring-aspects compile test-compile

2、在启动类上加上如下配置
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

【spring事务失效的几种场景以及原因】注: 如果是在idea上运行,则需做如下配置
spring事务失效的几种场景以及原因
文章图片

4、直接用TransactionTemplate
示例:
@Autowired private TransactionTemplate transactionTemplate; private void process(){ transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { processInTransaction(); } }); }

6、场景六:父子容器
失效原因: 子容器扫描范围过大,将未加事务配置的serivce扫描进来
解决方案:
1、父子容器个扫个的范围;
2、不用父子容器,所有bean都交给同一容器管理
注: 因为示例是使用springboot,而springboot启动默认没有父子容器,只有一个容器,因此就该场景就演示示例了
7、场景七:方法用final修饰
@Transactional public final boolean add(User user, UserService userService) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }

失效原因: 因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能
解决方案:
1、方法不要用final修饰
8、场景八:方法用static修饰
@Transactional public static boolean save(User user, UserService userService) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }

失效原因: 原因和final一样
解决方案:
1、方法不要用static修饰
9、场景九:调用本类方法
public boolean save(User user) { return this.saveUser(user); }@Transactional public boolean saveUser(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }

失效原因: 本类方法不经过代理,无法进行增强
解决方案:
1、注入自己来调用;
2、使用@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
10、场景十:多线程调用
@Transactional(rollbackFor = Exception.class) public boolean save(User user) throws ExecutionException, InterruptedException {Future future = executorService.submit(() -> { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new Exception(); } return isSuccess; }); return future.get(); }

失效原因: 因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务
11、场景十一:错误的传播行为
@Transactional(propagation = Propagation.NOT_SUPPORTED) public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }

失效原因: 使用的传播特性不支持事务
12、场景十二:使用了不支持事务的存储引擎
失效原因: 使用了不支持事务的存储引擎。比如mysql中的MyISAM
13、场景十三:数据源没有配置事务管理器
注: 因为springboot,他默认已经开启事务管理器。org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration。因此示例略过
14、场景十四:被代理的类过早实例化
@Service public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered {@Autowired private UserService userService; @Transactional public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }@Override public int getOrder() { return 1; } }

失效原因: 当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强
总结 本文列举了14种spring事务失效的场景,其实这14种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等
demo链接 https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case

    推荐阅读