• 从源码分析 Spring 基于注解的事务


    在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置:

        <!-- 拦截器方式配置事务 -->
        <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="add*" propagation="REQUIRED" />
                <tx:method name="append*" propagation="REQUIRED" />
                <tx:method name="insert*" propagation="REQUIRED" />
                <tx:method name="save*" propagation="REQUIRED" />
                <tx:method name="update*" propagation="REQUIRED" />
                <tx:method name="modify*" propagation="REQUIRED" />
                <tx:method name="edit*" propagation="REQUIRED" />
                <tx:method name="delete*" propagation="REQUIRED" />
                <tx:method name="remove*" propagation="REQUIRED" />
                <tx:method name="repair" propagation="REQUIRED" />
                <tx:method name="delAndRepair" propagation="REQUIRED" />
    
                <tx:method name="get*" propagation="SUPPORTS" />
                <tx:method name="find*" propagation="SUPPORTS" />
                <tx:method name="load*" propagation="SUPPORTS" />
                <tx:method name="search*" propagation="SUPPORTS" />
                <tx:method name="datagrid*" propagation="SUPPORTS" />
    
                <tx:method name="*" propagation="SUPPORTS" />
            </tx:attributes>
        </tx:advice>
        <aop:config>
            <aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" />
            <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
        </aop:config>

    这种方式明显的缺点是,不太容易理解,并且限定了service层的方法名称的前缀,没有模板的话写起来也很难,很容易写错。

    因此在spring中引入了基于注解的事务配置方法之后,我们应该抛弃这种事务配置方法了。基于注解 @Transactional 的事务配置具有简单,灵活的优点。下面看一个例子:

    @Service("userService")
    @Transactional
    public class UserServiceImpl implements UserService{
        @Autowired
        private UserMapper userMapper;
        
        @Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true)
        public User getUser(int userId) {
            return userMapper.getUser(userId);
        }
        
        @Transactional
        public void addUser(String username){
            userMapper.addUser(username);
    //      int i = 1/0;    // 测试事务的回滚
        }
        
        @Transactional (rollbackFor = BaseBusinessException.class)
        public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
            userMapper.addUser(username);        
            this.m1();        
            userMapper.deleteUserById(id);
        }
        
        private void m1() throws BaseBusinessException {
            throw new BaseBusinessException("xxx");
        }
    }

    首先在service类上声明了@Transactional,表明类中的所有方法都需要运行在事务中,然后在方法中可以指定具体的事务特性,方法中的@Transactional会覆盖类上的@Transactional。

    下面我们从源码的角度(从源码的学习可以给我们实打实的比较深入理解,而且不会出错,二手资料总是会有时延的)来探究一下它们:

    public @interface Transactional {
    
        /**
         * A qualifier value for the specified transaction.
         * <p>May be used to determine the target transaction manager,
         * matching the qualifier value (or the bean name) of a specific
         * {@link org.springframework.transaction.PlatformTransactionManager}
         * bean definition.
         */
        String value() default "";
    
        /**
         * The transaction propagation type.
         * Defaults to {@link Propagation#REQUIRED}.
         * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
         */
        Propagation propagation() default Propagation.REQUIRED;
    
        /**
         * The transaction isolation level.
         * Defaults to {@link Isolation#DEFAULT}.
         * @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
         */
        Isolation isolation() default Isolation.DEFAULT;
    
        /**
         * The timeout for this transaction.
         * Defaults to the default timeout of the underlying transaction system.
         * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
         */
        int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    
        /**
         * {@code true} if the transaction is read-only.
         * Defaults to {@code false}.
         * <p>This just serves as a hint for the actual transaction subsystem;
         * it will <i>not necessarily</i> cause failure of write access attempts.
         * A transaction manager which cannot interpret the read-only hint will
         * <i>not</i> throw an exception when asked for a read-only transaction.
         * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
         */
        boolean readOnly() default false;
    
        /**
         * Defines zero (0) or more exception {@link Class classes}, which must be a
         * subclass of {@link Throwable}, indicating which exception types must cause
         * a transaction rollback.
         * <p>This is the preferred way to construct a rollback rule, matching the
         * exception class and subclasses.
         * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
         */
        Class<? extends Throwable>[] rollbackFor() default {};
    
        /**
         * Defines zero (0) or more exception names (for exceptions which must be a
         * subclass of {@link Throwable}), indicating which exception types must cause
         * a transaction rollback.
         * <p>This can be a substring, with no wildcard support at present.
         * A value of "ServletException" would match
         * {@link javax.servlet.ServletException} and subclasses, for example.
         * <p><b>NB: </b>Consider carefully how specific the pattern is, and whether
         * to include package information (which isn't mandatory). For example,
         * "Exception" will match nearly anything, and will probably hide other rules.
         * "java.lang.Exception" would be correct if "Exception" was meant to define
         * a rule for all checked exceptions. With more unusual {@link Exception}
         * names such as "BaseBusinessException" there is no need to use a FQN.
         * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}
         */
        String[] rollbackForClassName() default {};
    
        /**
         * Defines zero (0) or more exception {@link Class Classes}, which must be a
         * subclass of {@link Throwable}, indicating which exception types must <b>not</b>
         * cause a transaction rollback.
         * <p>This is the preferred way to construct a rollback rule, matching the
         * exception class and subclasses.
         * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}
         */
        Class<? extends Throwable>[] noRollbackFor() default {};
    
        /**
         * Defines zero (0) or more exception names (for exceptions which must be a
         * subclass of {@link Throwable}) indicating which exception types must <b>not</b>
         * cause a transaction rollback.
         * <p>See the description of {@link #rollbackForClassName()} for more info on how
         * the specified names are treated.
         * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}
         */
        String[] noRollbackForClassName() default {};
    
    }
    public @interface Transactional

    注解@Transactional的属性有:propagation, isolation, timeout, readOnly, rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName

    propagation, isolation, timeout, readOnly都有默认值,而rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName默认值都是空的。

    我们具体看下我们可能会用到的属性:propagation, isolation, readOnly, rollbackFor

    1)propagation指定事务的传播属性

    public enum Propagation {
    
        /**
         * Support a current transaction, create a new one if none exists.
         * Analogous to EJB transaction attribute of the same name.
         * <p>This is the default setting of a transaction annotation.
         */
        REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    
        /**
         * Support a current transaction, execute non-transactionally if none exists.
         * Analogous to EJB transaction attribute of the same name.
         * <p>Note: For transaction managers with transaction synchronization,
         * PROPAGATION_SUPPORTS is slightly different from no transaction at all,
         * as it defines a transaction scope that synchronization will apply for.
         * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
         * will be shared for the entire specified scope. Note that this depends on
         * the actual synchronization configuration of the transaction manager.
         * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
         */
        SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    
        /**
         * Support a current transaction, throw an exception if none exists.
         * Analogous to EJB transaction attribute of the same name.
         */
        MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    
        /**
         * Create a new transaction, and suspend the current transaction if one exists.
         * Analogous to the EJB transaction attribute of the same name.
         * <p>Note: Actual transaction suspension will not work out-of-the-box on
         * all transaction managers. This in particular applies to JtaTransactionManager,
         * which requires the {@code javax.transaction.TransactionManager} to be
         * made available it to it (which is server-specific in standard J2EE).
         * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
         */
        REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    
        /**
         * Execute non-transactionally, suspend the current transaction if one exists.
         * Analogous to EJB transaction attribute of the same name.
         * <p>Note: Actual transaction suspension will not work on out-of-the-box
         * on all transaction managers. This in particular applies to JtaTransactionManager,
         * which requires the {@code javax.transaction.TransactionManager} to be
         * made available it to it (which is server-specific in standard J2EE).
         * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
         */
        NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    
        /**
         * Execute non-transactionally, throw an exception if a transaction exists.
         * Analogous to EJB transaction attribute of the same name.
         */
        NEVER(TransactionDefinition.PROPAGATION_NEVER),
    
        /**
         * Execute within a nested transaction if a current transaction exists,
         * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
         * <p>Note: Actual creation of a nested transaction will only work on specific
         * transaction managers. Out of the box, this only applies to the JDBC
         * DataSourceTransactionManager when working on a JDBC 3.0 driver.
         * Some JTA providers might support nested transactions as well.
         * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
         */
        NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
    
        private final int value;
    
    
        Propagation(int value) { this.value = value; }
    
        public int value() { return this.value; }
    
    }
    public enum Propagation

    propagation可以取如下的值:REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED

    我们一般会只用:REQUIRED(默认值), SUPPORTS,其它的取值基本上不使用(具体可以参考上面源码中的注释,已经很详细了)。

    propagation=Propagation.REQUIRED:表示该方法或类必须要事务的支持,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,那么就新建一个事务。

    propagation=Propagation.SUPPORTS:表示该方法或类支持事务,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,也可以。

    2)isolation指定事务的隔离级别

    public enum Isolation {
    
        /**
         * Use the default isolation level of the underlying datastore.
         * All other levels correspond to the JDBC isolation levels.
         * @see java.sql.Connection
         */
        DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
    
        /**
         * A constant indicating that dirty reads, non-repeatable reads and phantom reads
         * can occur. This level allows a row changed by one transaction to be read by
         * another transaction before any changes in that row have been committed
         * (a "dirty read"). If any of the changes are rolled back, the second
         * transaction will have retrieved an invalid row.
         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
         */
        READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
    
        /**
         * A constant indicating that dirty reads are prevented; non-repeatable reads
         * and phantom reads can occur. This level only prohibits a transaction
         * from reading a row with uncommitted changes in it.
         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
         */
        READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
    
        /**
         * A constant indicating that dirty reads and non-repeatable reads are
         * prevented; phantom reads can occur. This level prohibits a transaction
         * from reading a row with uncommitted changes in it, and it also prohibits
         * the situation where one transaction reads a row, a second transaction
         * alters the row, and the first transaction rereads the row, getting
         * different values the second time (a "non-repeatable read").
         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
         */
        REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
    
        /**
         * A constant indicating that dirty reads, non-repeatable reads and phantom
         * reads are prevented. This level includes the prohibitions in
         * {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation
         * where one transaction reads all rows that satisfy a {@code WHERE}
         * condition, a second transaction inserts a row that satisfies that
         * {@code WHERE} condition, and the first transaction rereads for the
         * same condition, retrieving the additional "phantom" row in the second read.
         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
         */
        SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
    
    
        private final int value;
    
    
        Isolation(int value) { this.value = value; }
    
        public int value() { return this.value; }
    
    }
    public enum Isolation

    isolation可以取如下的值:DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE

    我们一般只使用:DEFAULT(默认值),因为我们一般是直接在数据库的层面上来设置事务的隔离级别,很少会在应用层来设置隔离基本。

    isolation=Isolation.DEFAULT:表示使用下层数据库指定的隔离级别

    3)readOnly为只读,利于数据库优化器进行优化。默认值为 false。所以对于只对数据库进行读取的方法,我们可以如下指定:

        @Transactional (propagation=Propagation.SUPPORTS)
        public User getUser(int userId) {
            return userMapper.getUser(userId);
        }

    表示:有事务则使用当前事务,没有事务则不使用事务。最大限度的利于数据库的优化器进行优化。

    如果一定要使用事务的话,也可以这样使用readOnly=true来优化:

        @Transactional (propagation=Propagation.REQUIRED,readOnly=true)
        public User getUser(int userId) {
            return userMapper.getUser(userId);
        }

    这就是 readOnly 的作用。表示有事务则使用当前事务,如果没有事务,则新建一个只读事务。其实 上面的 propagation=Propagation.REQUIRED也是可以去掉的,因为他是默认值

        @Transactional (readOnly=true)
        public User getUser(int userId) {
            return userMapper.getUser(userId);
        }
    

    表示强制事务,并且是 只读事务。readOnly 要注意有点的是,readOnly只有在 有事务时,才会生效,如果没有事务,那么 readOnly 是会被忽略的。比如:

    @Transactional(propagation=Propagation.SUPPORTS, readonly=true)

    表示的就是 没有事务 也是可以的,有事务的话,就一定是 只读事务。

    4)rollbackFor,其实该属性也很少使用,而且经常被误用。表示抛出什么异常时,会回滚事务。异常分为受检异常(必须进行处理或者重新抛出)和非受检异常(可以不进行处理)。在遇到非受检异常时,事务是一定会进行回滚的。rollbackFor用于指定对于何种受检异常发生时,进行回滚。因为受检异常,我们必须进行处理或者重新抛出,所以只有一种情况下我们要使用rollbackFor来指定,就是我们不处理异常,直接抛出受检异常,并且我们需要方法在抛出该异常时,进行回滚,如上面的例子:

        @Transactional (rollbackFor = BaseBusinessException.class)
        public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
            userMapper.addUser(username);
            this.m1();
            userMapper.deleteUserById(id);
        }
        private void m1() throws BaseBusinessException {
            throw new BaseBusinessException("xxx");
        }

    因为 m1 方法抛出受检异常,我们在 addAndDeleteUser 方法中不对该异常进行处理,而是直接抛出,如果我们希望 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 要么都成功,要么都失败,此时我们则应该指定:rollbackFor = BaseBusinessException.class ,进行回滚。

    所以只有我们的方法声明要抛出一个受检异常时,我们才应该使用 rollbackFor 属性来进行处理。如果我们在 addAndDeleteUser  方法中对 m1 方法的受检异常进行了处理,那么就没有必要使用 rollbackFor 了:

        public void addAndDeleteUser(String username, int id){
            userMapper.addUser(username);
            try{
                this.m1();
            }catch(BaseBusinessException e){
                // 处理异常,比如记录进日志文件等
            }
            userMapper.deleteUserById(id);
        }

    因为我们处理了 m1 方法的异常,那么就不会有受检异常导致 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 这两个方法一个执行成功,一个没有执行。而非受检异常默认就会回滚。

    受检异常是必须进行处理或者重新声明抛出的。只有声明重新抛出受检异常时,才会需要使用 rollbackFor 属性。所以下面的方式就属于滥用 rollbackFor 了:

        @Transactional (rollbackFor=Exception.class)
        public void addAndDeleteUser2(String username, int id){
            userMapper.addUser(username);
            userMapper.deleteUserById(id);
        }

    因为:受检异常是必须进行处理或者重新声明抛出,而我们既没有进行处理,也没有重新抛出,就说明他绝对不可能会抛出受检异常了。而只会抛出未受检异常,而未受检异常,默认就会回滚,所以上面的 @Transactional (rollbackFor=Exception.class) 完全是多余的。

    总结

    1)@Transactional 的默认值为:@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,

    timeout=TransactionDefinition.TIMEOUT_DEFAULT),默认值已经适合绝大多数情况,所以我们一般使用 @Transactional 进行注解就够了。

    2)只有当默认值不符合我们的需要时才给@Transactional的属性指定值,一般也就指定:propagation=Propagation.SUPPORTS 和 readOnly=true,其它的属性和值一般很少使用非默认值。所以我们前面的UserServiceImpl类可以重构如下:

    @Service("userService")
    @Transactional
    public class UserServiceImpl implements UserService{
        @Autowired
        private UserMapper userMapper;
        
        @Transactional (readOnly=true)
        public User getUser(int userId) {
            return userMapper.getUser(userId);
        }
        
        public void addUser(String username){
            userMapper.addUser(username);
            int i = 1/0;    // 测试事务的回滚
        }
        
        public void deleteUser(int id){
            userMapper.deleteUserById(id);
    //      int i = 1/0;    // 测试事务的回滚
        }
        
        @Transactional (rollbackFor = BaseBusinessException.class)
        public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
            userMapper.addUser(username);
            this.m1();
            userMapper.deleteUserById(id);
        }
        
        private void m1() throws BaseBusinessException {
            throw new BaseBusinessException("xxx");
        }
    }

    addUser 和 deleteUser 因为会继承上的 @Transactional ,所以无需另外指定了,只有当类上指定的 @Transactional 不适合时,才需要另外在方法上进行指定。

    3)所以我们只实际情况中,我们只需要使用下面三者来进行注解事务的配置:

    @Transactional,

    @Transactional (readOnly=true),

    @Transactional (propagation=Propagation.SUPPORTS, readOnly=true),

    @Transactional (rollbackFor = xxException.class),其它都可以保持默认值,其它的非默认值极少使用。

    另外

    要使用@Transactional来进行注解事务配置,必须要在spring的配置文件中加入下面的配置说明,启用基于注解的配置:

        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        
        <!-- 使用annotation定义事务 -->
        <tx:annotation-driven transaction-manager="transactionManager" />

    补充说明

          也许你会觉得奇怪,userMapper.getUser(userId); 这些涉及到数据库访问的方法,为什么不会抛出 SQLException 受检异常呢?如果手动注册驱动,然后获取链接,进行数据库操作时,是会有许多的受检异常要处理的:

            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                // ...
            }
            String url="jdbc:mysql://localhost:3306/databasename";
             try {
                Connection conn = (Connection) DriverManager.getConnection(url,"username","password");
            } catch (SQLException e) {
                // ...
            } 

    哪为什么在这里userMapper.getUser(userId); 就不需要处理受检异常呢?其实这是spring的功能,spring为了将我们从那些十分麻烦的受检异常比如SQLException中解救处理,将所有的数据库访问层的受检异常转嫁到 Spring 的 非受检异常RuntimeException 体系中来——DataAccessException

  • 相关阅读:
    RPD Volume 168 Issue 4 March 2016 评论1
    初步接触CERNVM
    java中重载与重写的区别
    第三节 java 函数
    第二节 java流程控制(循环结构)
    第二节 java流程控制(判断结构+选择结构)
    JAVA 对象引用,以及对象赋值
    Java学习笔记整理第一章 java基本数据类型、修饰符、运算符
    plantuml的使用
    力扣 第210题
  • 原文地址:https://www.cnblogs.com/digdeep/p/4458067.html
Copyright © 2020-2023  润新知