美文网首页
Spring事务 - @Transactional的使用

Spring事务 - @Transactional的使用

作者: 丿星纟彖彳亍 | 来源:发表于2020-11-13 12:47 被阅读0次

    1、事务简单介绍

    Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式:

    1.1 编程式事务

    允许用户在代码中精确定义事务的边界。编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。

    对于编程式事务管理,spring推荐使用TransactionTemplate。

    1.2 声明式事务

    基于AOP,有助于用户将操作与事务规则进行解耦。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    声明式事务管理也有两种常用的方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional注解的方式。显然基于注解的方式更简单易用,更清爽。

    声明式事务管理明显要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。

    和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

    2、@Transactional介绍

    可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

    虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到 public 方法上,这是由Spring AOP的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

    默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

    2.1 @Transactional注解属性

    @Transactional注解里面的各个属性和Java事务属性里面是一一对应的。用来设置事务的传播行为、隔离规则、回滚规则、事务超时、是否只读。

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
    
        /**
         * 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
         */
        @AliasFor("transactionManager")
        String value() default "";
    
        /**
         * 同上。
         */
        @AliasFor("value")
        String transactionManager() default "";
    
        /**
         * 事务的传播行为,默认值为 REQUIRED。
         */
        Propagation propagation() default Propagation.REQUIRED;
    
        /**
         * 事务的隔离规则,默认值采用 DEFAULT。
         */
        Isolation isolation() default Isolation.DEFAULT;
    
        /**
         * 事务超时时间。
         */
        int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    
        /**
         * 是否只读事务
         */
        boolean readOnly() default false;
    
        /**
         * 用于指定能够触发事务回滚的异常类型。
         */
        Class<? extends Throwable>[] rollbackFor() default {};
    
        /**
         * 同上,指定类名。
         */
        String[] rollbackForClassName() default {};
    
        /**
         * 用于指定不会触发事务回滚的异常类型
         */
        Class<? extends Throwable>[] noRollbackFor() default {};
    
        /**
         * 同上,指定类名
         */
        String[] noRollbackForClassName() default {};
    }
    
    • 1、value、transactionManager属性
      它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

    大多数项目只需要一个事务管理器。然而,有些项目为了提高效率、或者有多个完全不同又不相干的数据源,从而使用了多个事务管理器。机智的Spring的Transactional管理已经考虑到了这一点,首先定义多个transactional manager,并为qualifier属性指定不同的值;然后在需要使用@Transactional注解的时候指定TransactionManager的qualifier属性值或者直接使用bean名称。

    配置和代码使用的例子:

    <tx:annotation-driven/>
     
    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource1"></property>
        <qualifier value="datasource1Tx"/>
    </bean>
     
    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource2"></property>
        <qualifier value="datasource2Tx"/>
    </bean>
    
    public class TransactionalService {
        @Transactional("datasource1Tx")
        public void setSomethingInDatasource1() { ... }
     
        @Transactional("datasource2Tx")
        public void doSomethingInDatasource2() { ... }
    }
    
    • 2、 rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName
      rollbackFor、rollbackForClassName用于设置那些异常需要回滚;noRollbackFor、noRollbackForClassName用于设置那些异常不需要回滚。他们就是在设置事务的回滚规则。

    2.2 @Transactional注解的使用

    @Transactional注解的使用关键点在理解@Transactional注解里面各个参数的含义。

    @Transactional注解内部实现依赖于Spring AOP编程。而AOP在默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为。

    2.2.1 @Transactional 注解必须添加在public方法上,private、protected方法上是无效的

    在使用@Transactional 的时候一定要记住,在private,protected方法上添加@Transactional 注解不会有任何效果。相当于没加一样。即使外部能调到protected的方法也无效。和没有添加@Transactional一样。

    2.2.2 函数之间相互调用

    2.2.2.1 同一个类中函数相互调用

    同一个类AClass中,有两个函数aFunction、aInnerFunction。aFunction调用aInnerFunction。而且aFunction函数会被外部调用。

    • 场景1 :
      aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常。
    public class AClass {
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        }
    
        private void aInnerFunction() {
            //todo: 操作数据B(做了增,删,改 操作)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数操作的数据都会回滚。
    
    • 场景2:两个函数都添加了@Transactional注解。aInnerFunction抛异常。
    public class AClass {
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
        private void aInnerFunction() {
            //todo: 操作数据B(做了增,删,改 操作)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
    
    • 场景3:aFunction不添加注解,aInnerFunction添加注解。aInnerFunction抛异常。
    public class AClass {
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        }
    
        @Transactional(rollbackFor = Exception.class)
        protected void aInnerFunction() {
            //todo: 操作数据B(做了增,删,改 操作)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
    
    • 场景4:
      aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常,不过在aFunction里面把异常抓出来了。
    //
    public class AClass {
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            try {
                aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private void aInnerFunction() {
            //todo: 操作数据B(做了增,删,改 操作)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数里面的数据库操作都成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。(当然了要看你添加的@Transactional注解有没有效)。
    
    2.2.2.2 不同类中函数相互调用

    两个类AClass、BClass。AClass类有aFunction、BClass类有bFunction。AClass类aFunction调用BClass类bFunction。最终会在外部调用AClass类的aFunction。

    • 场景1:aFunction添加注解,bFunction不添加注解。bFunction抛异常。
    @Service()
    public class AClass {
        private BClass bClass;
        @Autowired
        public void setbClass(BClass bClass) {
            this.bClass = bClass;
        }
    
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            bClass.bFunction();
        }
    }
    
    @Service()
    public class BClass {
        public void bFunction() {
            //todo: 数据库操作A(增,删,该)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数对数据库的操作都回滚了。
    
    • 场景2:aFunction、bFunction两个函数都添加注解,bFunction抛异常。
    @Service()
    public class AClass {
        private BClass bClass;
        @Autowired
        public void setbClass(BClass bClass) {
            this.bClass = bClass;
        }
    
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            bClass.bFunction();
        }
    }
    
    @Service()
    public class BClass {
        @Transactional(rollbackFor = Exception.class)
        public void bFunction() {
            //todo: 数据库操作A(增,删,该)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。这种情况下,你可以认为事务rollback了两次。两个函数都有异常。
    
    • 场景3:aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。
    @Service()
    public class AClass {
        private BClass bClass;
        @Autowired
        public void setbClass(BClass bClass) {
            this.bClass = bClass;
        }
    
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            try {
                bClass.bFunction();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    @Service()
    public class BClass {
        @Transactional(rollbackFor = Exception.class)
        public void bFunction() {
            //todo: 数据库操作A(增,删,该)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:两个函数数据库操作都没成功。而且还抛异常了。
    org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看打印出来的解释也很好理解把。咱们也可以这么理解,两个函数用的是同一个事务。bFunction函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让调了。
    
    • 场景4:
      aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为。表明是一个新的事务。其实咱们情况3就是来解决情况2的问题的。
    @Service()
    public class AClass {
        private BClass bClass;
        @Autowired
        public void setbClass(BClass bClass) {
            this.bClass = bClass;
        }
    
        @Transactional(rollbackFor = Exception.class)
        public void aFunction() {
            //todo: 数据库操作A(增,删,该)
            try {
                bClass.bFunction();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    @Service()
    public class BClass {
        @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
        public void bFunction() {
            //todo: 数据库操作A(增,删,该)
            throw new RuntimeException("函数执行有异常!");
        }
    }
    //结果:bFunction函数里面的操作回滚了,aFunction里面的操作成功了。有了前面情况2的理解。这种情况也很好解释。两个函数不是同一个事务了。
    

    3、总结

    • 要知道@Transactional注解里面每个属性的含义。@Transactional注解属性就是来控制事务属性的。通过这些属性来生成事务。

    • 要明确我们添加的@Transactional注解会不会起作用。@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。

    • 要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,还是沿用之前的事务。稍不注意就会抛UnexpectedRollbackException异常。

    checked和unchecked异常

    • checked异常:继承自java.lang.Exception(java.lang.RuntimeException除外)
      表示无效,不是程序中可以预测的。
      比如无效的用户输入,文件不存在,网络或者数据库连接错误,这些都是外在的原因,都不是程序内部可以控制的。
      必须在代码中显式地处理,比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛给调用站的上一层
    • unchecked异常:继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)
      表示错误,程序的逻辑错误。比如IllegalArgumentException, NullPointerException和IllegalStateException。
      不需要在代码中显示地捕获unchecked异常做处理。

    相关文章

      网友评论

          本文标题:Spring事务 - @Transactional的使用

          本文链接:https://www.haomeiwen.com/subject/vakvbktx.html