• 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 = " 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坐标以及相应插件

    <dependency>
    	<groupId>org.aspectj</groupId>
    	<artifactId>aspectjrt</artifactId>
    	<version>1.8.9</version>
    </dependency>
    
    <plugin>
    	<groupId>org.codehaus.mojo</groupId>
    	<artifactId>aspectj-maven-plugin</artifactId>
    	<version>1.9</version>
    	<configuration>
    		<showWeaveInfo>true</showWeaveInfo>
    		<aspectLibraries>
    			<aspectLibrary>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-aspects</artifactId>
    			</aspectLibrary>
    		</aspectLibraries>
    	</configuration>
    	<executions>
    		<execution>
    			 <goals>
                  <goal>compile</goal>       <!-- use this goal to weave all your main classes -->
                  <goal>test-compile</goal>  <!-- use this goal to weave all your test classes -->
                </goals>
    		</execution>
    	</executions>
    </plugin> 

    2、在启动类上加上如下配置

    @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

    注: 如果是在idea上运行,则需做如下配置

    在这里插入图片描述

    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<Boolean> 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

    原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

    如有侵权,请联系 yunjia_community@tencent.com 删除。

  • 相关阅读:
    Django基础(一)
    CSS
    HTML
    python之路_面向对象
    python之路第六篇
    python之路第四篇
    python之路第三篇
    python之路第二篇
    python之路第一篇
    hdu 3551(一般图的匹配)
  • 原文地址:https://www.cnblogs.com/konglxblog/p/16229394.html
Copyright © 2020-2023  润新知