• Spring事务失效的各种场景


    Spring事务失效的各种场景

    一、访问权限

    Java的访问权限主要是:private、default、protected、public,它们的权限则是依次变大。
    如果我们在开发的时候定义错误的访问权限,就会导致事务出现问题

    @Service
    public class DemoService {
        
        @Transactional
        private void query(Demo demo) {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    我们可以查看源码,可以明白,spring事务的实现AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

    二、方法用final修饰

    我们在学习的时候都知道,某个方法不想被子类继承的时候就会加上关键字final,这种写法正常来说是没有问题的,但是如果该方法被用上事务就不行了

    @Service
    public class DemoService {
    
        @Transactional
        public final void query(Demo demo) {
          
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样的话会导致事务的失效,因为spring事务底层实现使用了代理,aop,通过jdk的动态代理或者cglib,生成了代理类,在代理类中实现了事务功能,如果方法被final修饰,无法重写该方法,也就无法添加事务的功能了
    在这里插入图片描述
    像idea这里就提出了提示,同样的如果方法是static方法也是不行的
    在这里插入图片描述
    因为静态方法是属于类的,而不是属于对象的,无法重写静态方法所以也就不可能实现事务

    三、方法内部调用

    在同一个类的service中,调用其他的事务方法

    @Service
    public class DemoService {
    
        @Transactional
        public  void query(Demo demo) {
            save(demo);
        }
        
        @Transactional
        public void save(Demo demo) {
            
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看到,query方法调用了save的方法,由于spring的事务实现是因为aop生成代理,这样是直接调用了this对象,所以也不会生成事务。

    解决方法:
    1、增加一个service,把一个事务的方法移到新增加的service方法里面,然后进行注入再调用

    @Service
    public class DemoTwoService {
        
        @Transactional
        public void save(Demo demo) {
    
        }
    }
    
    @Service
    public class DemoService {
    
        @Autowired
        DemoTwoService demoTwoService;
    
        @Transactional
        public  void query(Demo demo) {
            demoTwoService.save(demo);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2、在自己类中注入自己

    @Service
    public class DemoService {
    
        @Autowired
        DemoService demoService;
    
        @Transactional
        public  void query(Demo demo) {
            demoService.save(demo);
        }
    
        @Transactional
        public void save(Demo demo) {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    由于这种写法基于spring的三级缓存不会导致,循环依赖的问题出现

    3、通过AopContentent

    @Service
    public class DemoService {
        
        @Transactional
        public  void query(Demo demo) {
            DemoService demoService = (DemoService)AopContext.currentProxy();
            demoService.save(demo);
        }
    
        @Transactional
        public void save(Demo demo) {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    四、没有被spring管理

    在使用spring事务的时候,对象要被spring进行管理,也就是需要创建bean,一般我们都会加@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。,如果忘记加了,也会导致,事务的失效

    五、多线程调用

    @Service
    public class DemoService {
    
        @Autowired
        DemoTwoService demoTwoService;
    
        @Transactional
        public  void query(Demo demo) {
            new Thread(()->{
                demoTwoService.save(demo);
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过上面的例子,我们可以知道,query调用了事务方法save,但是事务方法在另一个线程里面调用,这样会导致两个方法在不同的一个线程中,获取的数据库连接也不一样,所以会是两个不同的事务,如果save的方法抛出了异常,query回滚是不可能的,看过spring源码,我们可以知道,spring的事务是通过连接数据库来实现的,当前线程保存了一个map,key—数据源,value----数据库连接,事务其实就是指向同一个连接的,只有拥有同一个数据库连接才能同时提交和回滚,如果在不同的线程,数据库的连接不是同一个,所以事务也不是同一个。

    private static final ThreadLocal<Map<Object, Object>> resources =
    
      new NamedThreadLocal<>("Transactional resources");
    
    • 1
    • 2
    • 3

    六、设计的表不支持事务

    我们都知道,mysql5之前默认的数据库引擎是MyISAM,可能一些老的项目还在使用,但是他是不支持事务的

    七、没有开启事务

    如果创建的不是springboot项目可能会导致这样的问题出现,因为springboot项目有自动装配的类DataSourceTransactionManagerAutoConfiguration,已经默认开启了事务,配置spring.datasource参数就行,如果是spring项目,需要在applicationContext.xml配置的,不然事务不会生效

     
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
        <property name="dataSource" ref="dataSource">property> 
    bean> 
    <tx:advice id="advice" transaction-manager="transactionManager"> 
        <tx:attributes> 
            <tx:method name="*" propagation="REQUIRED"/>
        tx:attributes> 
    tx:advice> 
     
    <aop:config> 
        <aop:pointcut expression="execution(* com.demo.*.*(..))" id="pointcut"/> 
        <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
    aop:config> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    还有就是切点配错,也会导致事务失效

    八、错误的事务传播

    我们在使用@Transactional注解时,是可以指定propagation参数的。
    该参数的作用是指定事务的传播特性,
    spring目前支持7种传播特性:

    • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
    • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
    • MANDATORY 如果当前上下文中存在事务,否则抛出异常。
    • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
    • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
    • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
    • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
      如果我们在手动设置propagation参数的时候,把传播特性设置错了
    @Service
    public class DemoService {
    
        @Transactional(propagation = Propagation.NEVER)
        public  void query(Demo demo) {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们可以看到query的事务传播设置为了Propagation.NEVER,这种类型的传播不支持事务,会抛异常,
    目前支持事务的三种传播特性为:REQUIRED,REQUIRES_NEW,NESTED

    九、自己捕获了异常

    事务不回滚,可能是我们在写代码的时候自己在代码手动进行了try…catch

    @Transactional
        public  void query(Demo demo) {
            try {
                save(demo);
            } catch (Exception e) {
                System.out.println("异常");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这种情况下,spring事务不会进行回滚,因为我们进行了手动捕获异常,然后没有手动抛出,如果想要spring事务的正常回滚,必须抛出它能处理的异常,如果没有抛出异常,spring会认为程序没有问题。

    十、手动抛出别的异常

    我们没有手动捕获异常,但是如果抛出的异常不正确,spring事务也不会回滚。

     @Transactional
        public  void query(Demo demo) throws Exception{
            try {
                save(demo);
            } catch (Exception e) {
                throw new Exception(e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面我们捕获了异常,然后手动抛出Exception,事务同样不会回滚,因为spring事务,默认情况下不会回滚Exception(非运行时的异常),只会回滚RuntimeException(运行时异常)和Error(错误)。

    十一、自定义回滚异常

    在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。

    但如果这个参数的值设置错了,就会引出一些问题

      @Transactional(rollbackFor = BusinessException.class)
        public  void query(Demo demo) throws Exception{
                save(demo);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面是我们自定义的业务异常,如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
    因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。

    十二、嵌套事务回滚过头

    @Service
    public class DemoService {
    
        @Autowired
        private DemoTwoService demoTwoService;
    
        @Autowired
        private DemoDao demoDao;
        public
        @Transactional(rollbackFor = Exception.class)
        public  void query(Demo demo) throws Exception{
            demoDao.save(demo);
            demoTwoService.save(demo);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    原本只是希望回滚demoServise.save(),不回滚demoDao.save(demo);,但是这种情况两个都会被回滚,因为demoTwoService.save(demo);没有捕获异常,往上抛出,导致query进行回滚,所以同时回滚了两个
    解决方法:

    @Service
    public class DemoService {
    
        @Autowired
        private DemoTwoService demoTwoService;
    
        @Autowired
        private DemoDao demoDao;
        public
        @Transactional(rollbackFor = Exception.class)
        public  void query(Demo demo) throws Exception{
            demoDao.save(demo);
            try {
                demoTwoService.save(demo);
            } finally {
                
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

    十三、编程式事务

    上面的事务失效都是基于@Transactional注解的,我们把这种事务叫做:声明式事务。

    其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。

    @Service
    public class DemoService {
        
    
        @Autowired
        private TransactionTemplate transactionTemplate;
    
        @Transactional(rollbackFor = Exception.class)
        public  void query(Demo demo) throws Exception{
           transactionTemplate.execute((status -> {
               save(demo);
               return Boolean.TRUE;
           }));
        }
    
        @Transactional
        public void save(Demo demo) {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。

    使用TransactionTemplate的编程式事务可以

    • 避免由于spring aop问题,导致事务失效的问题。
    • 能够更小粒度的控制事务的范围。

    tips:关于本文举例的query加事务不太符合开发的要求,但是有点懒就不改了

    在这里插入图片描述

  • 相关阅读:
    使用python setup.py报错:Upload failed (403) / Upload failed (400)
    Tomcat的安装和使用
    2023年9月14日
    《向量数据库指南》——向量数据库与 ANN 算法库的区别
    高效理解 FreeSql WhereDynamicFilter,深入了解设计初衷[.NET ORM]
    Java动态代理Proxy类
    对极几何(Epipolar Geometry)
    报表工具,应该适用哪种部署方式?_光点科技
    数字孪生技术如何提高化工生产安全性?
    从零搭建开发脚手架 轻量级链路追踪Trace
  • 原文地址:https://blog.csdn.net/qq_43803285/article/details/125811821