从源码分析 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 的作用。表示有事物则使用当前事物,如果没有事物,则新建一个只读事物。

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 (rollbackFor = XXXException.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" />

时间: 2024-11-08 10:35:40

从源码分析 Spring 基于注解的事物的相关文章

【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理

Java及Spring Boot新手,首次尝试源码分析,欢迎指正! 一.概述 @EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关.本文将从@EnableAutoConfiguration入手,尝试通过源码分析增强对Spring Boot的理解. 所用版本:Spring Boot 2.2.0.M5 + Spring Framework 5.2.0.RC1 1. 功能的实现:(Spring Boot部分) boot.autoconfigure.Enab

ArrayList部分源码分析(基于1.8)

今天分析第一个集合类:ArrayList 首先,说一下我读这部分源码的感受. ArrayList类底层实现实际上是数组,因此很多操作会调用很多本地(Native)方法来实现或者部分实现.用java实现的很多方法中,只是用java代码进行了一些必要的逻辑判断和变量值的改变. 在AbstractList中加入的modCount变量是为了配合迭代器的使用. 下面是部分源码分析: package java.util; import java.util.function.Consumer; import

Collection架构源码分析(基于1.8)

Collection接口有三个子接口,我们主要来分析一下其中的两种:List和Set List:有序集合,其中元素可以重复. Set:无序集合,元素不可以重复. List和Set两个接口都各自的抽象实现类. Collection源码分析 源码中的API: public interface Collection<E> extends Iterable<E> { int size(): boolean isEmpty()(); boolean contains(Object o); I

java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)

1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用到了cas相关的操作来保证线程安全的. 2.概述 ConcurrentHashMap这个类在java.lang.current包中,这个包中的类都是线程安全的.ConcurrentHashMap底层存储数据的结构与1.8的HashMap是一样的,都是数组+链表(或红黑树)的结构.在日常的开发中,我们

spring源码分析之cache注解

Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果. Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储

【Spring】从源码分析Spring配置文件的加载

使用Spring必须在web.xml中写如下配置: <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-valu

dubbo源码分析之基于SPI的强大扩展

https://blog.csdn.net/luoyang_java/article/details/86609045 Dubbo采用微内核+插件体系,使得设计优雅,扩展性强.那所谓的微内核+插件体系是如何实现的呢!大家是否熟悉spi(service providerinterface)机制,即我们定义了服务接口标准,让厂商去实现(如果不了解spi的请谷歌百度下), jdk通过ServiceLoader类实现spi机制的服务查找功能 1.为什么不使用JDK SPI在dubbo中它实现了一套自己的

HashMap源码分析(基于JDK1.6)

在Java集合类中最常用的除了ArrayList外,就是HashMap了.本文尽自己所能,尽量详细的解释HashMap的源码.一山还有一山高,有不足之处请之处,定感谢指定并及时修正. 在看HashMap源码之前先复习一下数据结构. Java最基本的数据结构有数组和链表.数组的特点是空间连续(大小固定).寻址迅速,但是插入和删除时需要移动元素,所以查询快,增加删除慢.链表恰好相反,可动态增加或减少空间以适应新增和删除元素,但查找时只能顺着一个个节点查找,所以增加删除快,查找慢.有没有一种结构综合了

深入源码分析Spring中的构造器注入

# 1. 示例 构造器注入类,分别有三个构造器,一个是无参构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器: public class ConstructorAutowiredTest { private User user; private Role role; public ConstructorAutowiredTest() { } public ConstructorAutowiredTest(User user) { this.user = user; } pub