Spring事务篇

作者: Snipers_onk | 来源:发表于2019-11-02 17:26 被阅读0次

    写在最前

    这些天看了一些关于Spring事务的文章,发现写的都很浅,基本上说一下事务特性和Spring事务传播行为就完事了,但是实际上事务不仅有这些,事务的来龙去脉没有讲清楚,具体原理也没有说。

    现在最新Spring版本已经到了5.2.0,翻了下源码,事务这块核心代码和4.x版本变化不大,所以本文的源码使用了Spring-tx:4.2.5.RELEASE。

    什么是事务

    事务存在的目的

    在日常开发中,我们在数据库中保存数据,在对数据库操作的过程中,为了使数据始终保持“正确”的状态,我们要对访问操作进行一些限制,以保证数据的完整性。

    事务就是以可控的方式对数据进行访问的一组操作,为了保证事务执行前后,数据库中数据始终处于“正确”的状态,事务本身有4个限定属性,也就是事务常说的ACID属性。

    • 原子性(Atomicity)

      原子性指事务中所包含的全部操作是一个不可分割的整体,要不全部提交成功,只要有一个失败,要么就全部失败。

    • 一致性(Consistency)

      一致性指的是事务中所包含的操作不能违反数据库的一致性检查,数据在事务执行之前处于某个数据的一致性状态,那么在数据执行之后也要保持数据间的一致性状态。

      最常用的例子就是银行转账,A给B转账,转账之前和转账之后两个账户的总额是不变的。

    • 隔离性(Isolation)

      事务的隔离性主要规定了各个事务之间相互影响的程度。隔离性主要面对数据的并发访问,并兼顾影响事务的一致性。当多个事务同时访问同一个数据时,不同的隔离级别决定了各个事务对数据访问的不同行为。一般事务的隔离级别有四种,从弱到强分别是Read Uncommitted,Read Committed,Repeatable Read和Serializable。

      • Read Uncommitted

        Read Uncommitted是最低级别的隔离,直接效果就是一个事务可以读取另外一个事务没有提交的更新结果。Read Uncommitted是以较低的隔离来寻求较高的性能,其本身无法回避几下几个问题:

        • 脏读(Dirty Read)。如果一个事务对数据进行了更新,但事务还有提交,另一个事务就可以看到该事务没有提交的更新结果。这样造成的问题就是,如果第一个事务发生回滚,那么第二个事务在此之前看到的数据就是一笔脏数据。
        • 不可重复读取(Non-Repeatable Read)。如果同一个事务在整个事务过程中对同一笔数据数据进行读取,每次读取结果都不同。如果事务1在事务2的更新造作之前读取一次数据,在事务2的更新造作之后对同一笔数据又读取一次,两次结果是不同的。
        • 幻读(Phantom Read)。幻读是指同样一个查询,在整个事务多次执行后,查询到的结果集不一样。如果事务1在事务2的插入或删除之前读取一次数据,在事务2的插入或删除造作之后对同一笔数据又读取一次,两次结果集是不同的。
      • Read Committed

        Read Committed是大部分数据库默认的隔离级别, 在该隔离级别下,一个事务的更新操作提交之后,其他事务才可以读到该事务的更新结果。所以Read Committed可以避免脏读问题,但是无法避免不可重复读取和幻读。

      • Repeatable Read

        Repeatable Read可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。Repeatable Read避免了脏读和不可重复读,但是无法避免幻读。

      • Serializable

        Serializable是最严格的隔离级别。所有事务操作都必须依次进行,可以避免所有问题,是最安全的隔离级别,但也是性能最差的隔离级别。

    • 持久性(Durability)

      事务的持久性是指,事务一旦提交,就会被持久的记录下来。

    Java事务处理

    在Java的事务场景中,事务处理操作会随着数据访问技术的不同而各异,我们不是使用专用的事务API来管理事务,而是通过当前数据访问技术提供的基于connection的API来管理事务,比如JDBC时Java平台访问关系型数据库最基础的API(伪代码):

    Connection connection = null;
    try{
        connection.setAutoCommit(false);
        connection = dataSource.getConnection;
        ...
        connection.commit();
    }finally{
        ...
        connection.close();
    }
    

    再比如我们使用Hibernate进行数据访问,就要使用Hibernate的Session进行数据访问时的事务管理。(伪代码)

    Session session = null;
    Transaction transaction = null;
    try{
        sessionFactory = sessionFactory.openSession();
        transaction = session.beginTransaction();
        transaction.commit();
    }finally{
        session.close():
    }
    

    由于不同的数据访问技术提供了不同的数据访问API来管理事务,这样会产生一些问题:

    1. 各种数据访问方式只提供了简单的事务API,但没有更高层次的抽象来帮助我们隔离事务与数据访问的过紧耦合。
    2. 事务处理过程中的异常应该都是不可恢复的,所以应该抛出unchecked exception,并且有一个统一的父类便于客户端处理。但现在没有一个统一的事务相关异常体系,我们需要捕捉特定API异常并处理。
    3. 对于开发人员,所谓事务的管理,最多也就是界定一下事务的边界,规定什么时候开始,什么时候结束。但没有一个统一的方式对事务进行管理。

    Spring事务架构

    Spring的事务框架理念的基本原则是:让事务管理的关注点与数据访问关注点相分离。

    • 当业务层使用的抽象API进行事务界定的时候,不需要关心操作的事务资源是什么,对不同的事务资源管理由相应的框架核心来负责。
    • 对数据访问层对数据进行访问时,只需要使用数据访问API进行数据访问,不需要关心当前事务资源如何参与事务或者如何参与事务,同样由框架负责。

    以上两点清晰的分离出来之后,我们只要关心通过抽象后的事务管理API对当前事务进行界定。不管数据访问方式如何切换,事务管理的方式都不需要改变。

    我们一般将事务管理放在Service层,而不是DAO层,是为了提高数据访问逻辑的可重用性,也可以再Service层根据相应逻辑决定提交事务或回滚,一般在业务方法中会调用多个数据访问的方法。

    Spring事务接口

    Spring事务抽象一共有3个主要接口,他们都在org.springframework.transaction包下。 PlatformTransactionManager是Spring事务框架的核心接口,它为应用程序提供事务边界的统一方式,它根据TransactionDefinition的定义来开启相关事务。TransactionDefinition负责定义事务相关属性,包括隔离级别、传播行为等。事务开启期间的事务状态由TransactionStatus负责,也可以通过TransactionStatus对事务进行有限的控制。下面对三个接口分别做介绍之前,先试着实现自定义的PlatformTransactionManager。

    自定义PlatformTransactionManager

    我们一般将事务管理放在Service层,而不是DAO层,是为了提高数据访问逻辑的可重用性,也可以再Service层根据相应逻辑决定提交事务或回滚,一般在业务方法中会调用多个数据访问的方法。以java.sql.Connection为例,在一个service方法中,不管调用几个Dao层方法,使用的都应该是同一个Connection,我们可以采用传递connection的方式。

    public void serviceMethod(){
        Object txObject = transactionManager.beginTransaction();
        Connection conn = (Connection)txObject;
        dao1.doDataAccess(conn);
        dao2.doDataAccess(conn);
        transactionManager.commit(txObject);
    }
    

    但这样有一个问题,还是没有摆脱Connection,如果要换成Hibernate,就需要转成Session。传递Connection的理念是对的,更好的做法是在事务开始之前获取一个Connection,然后将这个Connection绑定到当前线程。在需要使用Connection的时候,从当前线程获取,事务提交后,解除和当前线程的绑定。

    public class TransactionResourceManager {
        private static ThreadLocal resources = new ThreadLocal();
    
        public static Object getResources() {
            return resources.get();
        }
    
        public static void bindResources(Object resource){
            resources.set(resource);
        }
    
        public static Object unbindResource(){
            Object res = getResources();
            resources.set(null);
            return res;
        }
    }
    
    public class JdbcTransactionManager implements PlatformTransactionManager {
    
        private DataSource dataSource;
        public JdbcTransactionManager(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        @Override
        public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
            Connection conn;
            try{
                conn = dataSource.getConnection();
                TransactionResourceManager.bindResources(conn);
                return new DefaultTransactionStatus(conn,true,true,false,true,null);
            }catch (Exception e){
                throw new CannotCreateTransactionException("不能创建事务"+ e );
            }
        }
    
        @Override
        public void commit(TransactionStatus status) throws TransactionException {
            Connection conn = (Connection)TransactionResourceManager.unbindResource();
            try {
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void rollback(TransactionStatus status) throws TransactionException {
            Connection conn = (Connection)TransactionResourceManager.unbindResource();
            try {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    这样,在开始和结束时期间,Connection都可以通过TransactionResourceManager获得,Dao层也可以通过这种方式获取Connection进行数据访问了。

    TransactionDefinition

    事务属性

    TransactionDefinition主要定义了有哪些事务属性可以指定,包括

    事务的隔离级别

    TransactionDefinition定义了5个常量用于表示事务的隔离级别

    //数据库默认的隔离级别
    int ISOLATION_DEFAULT = -1;
    //以下4中分别对应数据库中隔离级别
    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
    
    事务的传播行为

    事务的传播行为表示事务处理过程中,所跨越的业务对象将以什么样的行为参与事务。在TransactionDefinition中定义了7种事务传播行为

    /*
    默认的事务传播行为,如果存在当前事务,则加入,如果不存在事务,则创建一个新事务。
    */
    int PROPAGATION_REQUIRED = 0;
    
    /*
    如果当前存在一个事务,则加入当前事务,如果不存在,则直接执行。
    对于一些查询方法来说,PROPAGATION_SUPPORTS比较适合,如果当前方法被其他方法调用,而其他方法启动了一个事务,此行为可以保证当前方法加入当前事务,并洞察当前事务对数据资源所做的更新。其他传播方式则看不到更新,因为当前事务没有提交。
    */
    int PROPAGATION_SUPPORTS = 1;
    
    /*
    PROPAGATION_MANDATORY强制要求当前存在一个事务,如果不存在,则抛出异常。
    */
    int PROPAGATION_MANDATORY = 2;
    
    /*
    不管当前是否存在事务, 都会创建一个新的事务。如果当前存在事务,会将当前事务挂起。如果某个业务对象不想影响外层事务,可以选择PROPAGATION_REQUIRES_NEW。
    */
    int PROPAGATION_REQUIRES_NEW = 3;
    
    /*
    不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务,则将当前事务挂起,但要看PlatformTransactionManager的实现类是否支持事务的挂起。
    */
    int PROPAGATION_NOT_SUPPORTED = 4;
    
    /*
    永远不支持事务,如果存在事务,则抛出异常。
    */
    int PROPAGATION_NEVER = 5;
    
    /*
    如果存在事务,则在当前事务的一个嵌套事务中执行。与PROPAGATION_REQUIRES_NEW类似,但与PROPAGATION_REQUIRES_NEW创建的事务与原有事务为同一档次,在新事务运行时原有事务被挂起;PROPAGATION_NESTED创建的事务寄生在外层事务,地位比外层事务低,嵌套事务执行时外层事务也处于活跃状态。
    */
    int PROPAGATION_NESTED = 6;
    
    事务的超时时间
    //默认为-1,不超时
    int TIMEOUT_DEFAULT = -1;
    int getTimeout();
    
    事务的只读属性
    boolean isReadOnly();
    

    事务属性实现类

    TransactionDefinition 相关实现类

    按照编程式事务场景和声明式事务场景划分为两个分支。DefaultTransactionDefinitionTransactionDefinition的默认实现类,它提供了各事务属性的默认值,并且通过它的setter方法,我们可以更改这些默认值。

        private int propagationBehavior = PROPAGATION_REQUIRED;
        private int isolationLevel = ISOLATION_DEFAULT;
        private int timeout = TIMEOUT_DEFAULT;
        private boolean readOnly = false;
    

    TransactionTemplate是Spring提供的编程式事务管理的模板类,它直接继承了DefaultTransactionDefinition。所以我们可以通过使用TransactionTemplate本身 提供事务控制属性。

    public class TransactionTemplate extends DefaultTransactionDefinition
            implements TransactionOperations, InitializingBean {
        ...
        public PlatformTransactionManager getTransactionManager() {
            return this.transactionManager;
        }
        ...
    }
    

    TransactionTemplate是继承自TransactionDefinition的接口定义,主要面向使用Spring AOP进行声明式事务的场合。它在TransactionDefinition的基础上定义了rollbackOn接口方法,这样就可以指定抛出哪些异常可以回滚事务。

    public interface TransactionAttribute extends TransactionDefinition {
        String getQualifier();
        boolean rollbackOn(Throwable ex); 
    }
    
    DefaultTransactionAttribute 相关实现类

    DefaultTransactionAttribute 实现了TransactionAttribute接口,有两个直接子类Ejb3TransactionAttribute,RuleBasedTransactionAttribute

    RuleBasedTransactionAttribute允许我们同时指定多个回滚规则,这些规则以RollbackRuleAttribute或者NoRollbackRuleAttribute的List形式存在。

    public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
        private List<RollbackRuleAttribute> rollbackRules;
        
       /**
         * Set the list of {@code RollbackRuleAttribute} objects
         * (and/or {@code NoRollbackRuleAttribute} objects) to apply.
         * @see RollbackRuleAttribute
         * @see NoRollbackRuleAttribute
         */
        public void setRollbackRules(List<RollbackRuleAttribute> rollbackRules) {
            this.rollbackRules = rollbackRules;
        }
        
        public boolean rollbackOn(Throwable ex) {
    
            RollbackRuleAttribute winner = null;
            int deepest = Integer.MAX_VALUE;
    
            if (this.rollbackRules != null) {
                for (RollbackRuleAttribute rule : this.rollbackRules) {
                    int depth = rule.getDepth(ex);
                    if (depth >= 0 && depth < deepest) {
                        deepest = depth;
                        winner = rule;
                    }
                }
            }
    
            // User superclass behavior (rollback on unchecked) if no rule matches.
            if (winner == null) {
                logger.trace("No relevant rollback rule found: applying default rules");
                return super.rollbackOn(ex);
            }
    
            return !(winner instanceof NoRollbackRuleAttribute);
        }
    }
    

    TransactionStatus

    TransactionStatus接口定义整个事务处理过程中的事务状态,更多时候,我们在编程式事务中使用。

    public interface TransactionStatus extends SavepointManager, Flushable {
    
        boolean isNewTransaction();
    
        //如果相应的PlatformTransactionManager支持Savepoint,则可以在当前事务中嵌套内联事务
        boolean hasSavepoint();
    
        //标记当前事务使其回滚
        void setRollbackOnly();
    
        boolean isRollbackOnly();
    
        void flush();
    
        boolean isCompleted();
    }
    
    TransactionStatus继承层次图

    AbstractTransactionStatus

    AbstractTransactionStatusTransactionStatus的抽象实现,为继承的子类提供一些共同抽象部分代码。DefaultTransactionStatus是Spring框架中TransactionStatus的主要实现类,各个TransactionManager的实现,大都借助于DefaultTransactionStatus来记载事务信息。

    PlatformTransactionManager

    PlatformTransactionManager是事务抽象框架的核心组件,对事务界定进行统一抽象。接口方法如下:

    public interface PlatformTransactionManager {
        TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
        void commit(TransactionStatus status) throws TransactionException;
        void rollback(TransactionStatus status) throws TransactionException;
    }
    

    PlatformTransactionManager的实现类

    整个PlatformTransactionManager抽象体系基于Strategy模式,由PlatformTransactionManager,具体的界定策略实现由具体的实现类负责,实现可以划分为面向局部事务和面向全局事务两个分支。

    面向局部事务实现类

    Spring为各种数据访问技术提供了现成的实现支持,如提供了实现类。根据这些实现类,在使用Spring框架进行事务管理的时候,只需要选择对应的PlatformTransactionManager的实现类即可。

    技术访问技术 PlatformTransactionManager的实现类
    JDBC/Mybatis DataSourceTransactionManager
    Hibernate HibernateTransactionManager
    JPA JpaTransactionManager
    ... ...
    面向全局事务实现类

    待补充

    剖析DataSourceTransactionManager

    PlatformTransactionManager的实现类遵循统一的结构和理念,所以选择DataSourceTransactionManager作为切入点。但是有几个概念需要提前引入,因为后文会用到

    • transaction objet

      transaction objet承载了当前事务的必要信息,PlatformTransactionManager实现类可以根据transaction objet所提供的信息来决定如何处理当前的事务。transaction objet的概念类似于JTA(Java Transaction API)规范中的javax.transaction.Transaction

    • TransactionSynchronization

      TransactionSynchronization是可以注册到事务处理过程的回调接口。它就像事务处理的事件监听器,当事务处理的某些规定时点发生时,会调用TransactionSynchronization上的一些方法来执行相应的回调,如在事务完成后清理相应的系统资源等。transaction objet的概念类似于JTA(Java Transaction API)规范中的javax.transaction.Synchronization

    • TransactionSynchronizationManager

      通过TransactionSynchronizationManager来管理TransactionSynchronization、当前事务状态以及具体的事务资源。比如java.sql.Connection或者hibernateSession需要绑定到线程,TransactionSynchronizationManager就是最终的目的地,更多的是对TransactionSynchronization的管理。

    下图是PlatformTransactionManager的继承层次,从各个实现类的继承层次来看,Spring事务框架更多的依赖于模板方法模式。PlatformTransactionManager主要关注事务处理过程中的主体逻辑,具体和事务资源相关的处理由子类来实现。

    PlatformTransactionManager继承层次

    主要体现在以下几个方法中:

    1. getTransaction

    获取TransactionStatus

    public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        
        /*
        获取Object transaction,以判断是否存在当前事务。
        Object接受是因为此时不同的TransactionManager会返回不同的TransactionObject,
        比如DataSourceTransactionManager返回DataSourceTransactionObject。
        doGetTransaction()是模板方式,公开给子类去实现。
        */
        Object transaction = doGetTransaction();
    
        //检查TransactionDefinition,如果为空,则创建一个默认的DefaultTransactionDefinition
        if (definition == null) {
            // Use defaults if no transaction definition given.
            definition = new DefaultTransactionDefinition();
        }
    
        //判断当前是否存在事务,默认返回false,需要子类重写此方法
        if (isExistingTransaction(transaction)) {
            //如果存在事务,由此方法检查传播行为做出不同的处理并返回TransactionStatus
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }
    
        // 检查默认事务过期时间
        if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
        }
    
        // 如果不存在事务,抛出异常
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        //如果是以下几种传播方式,开启新事务
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            //如果由注册的Synchronization,需要先将这些如果由注册的Synchronization挂起
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
            }
            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                DefaultTransactionStatus status = newTransactionStatus(
                    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException ex) {
                resume(null, suspendedResources);
                throw ex;
            }
            catch (Error err) {
                resume(null, suspendedResources);
                throw err;
            }
        }
        else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                            "isolation level will effectively be ignored: " + definition);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
        }
    }
    
    2. rollback
    public final void rollback(TransactionStatus status) throws TransactionException {
        //如果已经提交或者回滚,则证明事务已经完成,抛出异常
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
        }
    
        DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
        processRollback(defStatus); //见下面方法
    }
    
    private void processRollback(DefaultTransactionStatus status) {
        try {
            try {
                //触发事件
                triggerBeforeCompletion(status);
                //如果是嵌套事务,则通过status释放SavePoint
                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Rolling back transaction to savepoint");
                    }
                    status.rollbackToHeldSavepoint();
                }
                //如果是一个新事务,则调用子类的doRollback进行真正的回滚,doRollback是个抽象方法
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction rollback");
                    }
                    doRollback(status);
                }
                //如果当前存在事务
                else if (status.hasTransaction()) {
                    //rollbackOnly状态被设置,则带哦用子类的doSetRollbackOnly
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        doSetRollbackOnly(status);
                    }
                    else {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                        }
                    }
                }
                else {
                    logger.debug("Should roll back transaction but cannot - no transaction available");
                }
            }
            catch (RuntimeException ex) {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                throw ex;
            }
            catch (Error err) {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                throw err;
            }
            //触发事件
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
        }
        finally {
            /*
            清理事务资源等一系列操作,包括:
            1.设置DefaultTransactionStatus为完成状态
            2.清理与事务相关的Synchronization
            3.释放事务资源,对于DataSourceTransactionManager来说,关闭数据库连接,接触对应数据的绑定。
            4.如果之前由挂起的事务,则恢复挂起的事务
            */
            cleanupAfterCompletion(status);     
        }
    }
    
    3. commit

    提交事务

    public final void commit(TransactionStatus status) throws TransactionException {
        //如果事务已经完成,则抛出异常
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
        }
    
        DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
        
        //如果设置了只读,则进行回滚,否则进行正常提交操作
        if (defStatus.isLocalRollbackOnly()) {
            if (defStatus.isDebug()) {
                logger.debug("Transactional code has requested rollback");
            }
            processRollback(defStatus);
            return;
        }
        //提前检测全局rollbackOnly标志,如果满足这两个条件则抛出异常
        if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
            if (defStatus.isDebug()) {
                logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
            }
            processRollback(defStatus);
            // Throw UnexpectedRollbackException only at outermost transaction boundary
            // or if explicitly asked to.
            if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
                throw new UnexpectedRollbackException(
                    "Transaction rolled back because it has been marked as rollback-only");
            }
            return;
        }
    
        processCommit(defStatus);
    }
    
    private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;
            try {
                prepareForCommit(status);
                triggerBeforeCommit(status);
                triggerBeforeCompletion(status);
                beforeCompletionInvoked = true;
                boolean globalRollbackOnly = false;
                if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
                    globalRollbackOnly = status.isGlobalRollbackOnly();
                }
                //如果status持有savePoint,则释放它。实际上为处理嵌套事务的提交
                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    status.releaseHeldSavepoint();
                }
                /*
                如果为一个新事物,调用子类实现。对于DataSourceTransactionManager来说,
                事务的提交由connection来负责,所以会调用connection.commit();
                */
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    doCommit(status);
                }
                // Throw UnexpectedRollbackException if we have a global rollback-only
                // marker but still didn't get a corresponding exception from commit.
                if (globalRollbackOnly) {
                    throw new UnexpectedRollbackException(
                        "Transaction silently rolled back because it has been marked as rollback-only");
                }
            }
            catch (UnexpectedRollbackException ex) {
                // can only be caused by doCommit
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
                throw ex;
            }
            catch (TransactionException ex) {
                // 如果rollbackOnCommitFailure设置为true,则需要在抛出异常时进行事务回滚。默认为false
                if (isRollbackOnCommitFailure()) {
                    doRollbackOnCommitException(status, ex);
                }
                else {
                    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                }
                throw ex;
            }
            catch (RuntimeException ex) {
                if (!beforeCompletionInvoked) {
                    triggerBeforeCompletion(status);
                }
                doRollbackOnCommitException(status, ex);
                throw ex;
            }
            catch (Error err) {
                if (!beforeCompletionInvoked) {
                    triggerBeforeCompletion(status);
                }
                doRollbackOnCommitException(status, err);
                throw err;
            }
    
            // Trigger afterCommit callbacks, with an exception thrown there
            // propagated to callers but the transaction still considered as committed.
            try {
                triggerAfterCommit(status);
            }
            finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }
    
        }
        finally {
            /*
            做最后的清理工作,参照rolleback
            */
            cleanupAfterCompletion(status);
        }
    }
    

    还有两个模板方法没有列出来,分别是suspend()resume()。源码注释写的很清楚,也很好理解,这里就不再贴出源码了。

    使用Spring进行事务管理

    编程式事务管理

    使用编程式事务管理由两种方式,分别是直接PlatformTransactionManager或者使用TransactionTemplate。一般来说,Spring团队推荐使用TransactionTemplate进行编程式事务管理。

    PlatformTransactionManager

    只要为TransactionManager提供合适的实现,然后结合TransactionDefinition开启事务,最后根据TransactionStatus来回滚或提交事务,就可以完成对当前对象整个事务管理。

    import org.apache.log4j.Logger;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:spring/applicationContext.xml" })
    public class TransactionTest {
    
        @Resource
        private PlatformTransactionManager txManager;
    
        @Resource
        private DataSource dataSource;
        private static JdbcTemplate jdbcTemplate;
        Logger logger= Logger.getLogger(TransactionTest.class);
    
        private static final String INSERT_SQL = "insert into student(name) values (?)";
        private static final String COUNT_SQL = "select count(*) from student";
    
        @Test
        public void testdelivery(){
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            //事务的隔离级别
            def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            //事务的传播行为
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 
            //设置超时
            def.setTimeout(20);
            TransactionStatus status = txManager.getTransaction(def);
            jdbcTemplate = new JdbcTemplate(dataSource);
            int i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
            System.out.println("表中记录总数:"+i);
    
            try{
                jdbcTemplate.update(INSERT_SQL, "1");
                txManager.commit(status);  //提交status中绑定
            }catch (RuntimeException e){
                txManager.rollback(status); //事务完成
                throw e;        //此处需要抛出异常,否则好像什么都没发生一样
            }
    
            i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
            System.out.println("表中记录总数:" + i);
        }
    }
    

    虽然PlatformTransactionManager 屏蔽了不同的事务管理API差异,但是缺点还是很明显的:需要自己对异常进行处理,在每个需要事务管理的地方,都会出现大量的重复代码。所以就有了TransactionTemplate这种更方便的编程式事务。

    TransactionTemplate

    TransactionTemplate对与PlatformTransactionManager相关的事务界定操作以及相关的异常处理做了模板化封装,开发人员更多的关注于通过相应的Callback接口提供具体的事务界定内容即可。Spring针对TransactionTemplate提供了两个Callback接口:TransactionCallbackTransactionCallbackWithoutResult,二者的唯一区别就是是否需要返回结果集。

    使用TransactionTemplate进行事务管理的代码,看起来要比直接使用PlatformTransactionManager容易的多。

    @Autowired
    TransactionTemplate txTemplate;     //需要在xml文件中配置TransactionTeplate,这时才可以注入
    
    public void xxxService{
        Object result1 = txTemplate.execute(new TransactionCallback<Object>() {
            public Object doInTransaction(TransactionStatus status) {
                //事务操作
                return new Object();
            }
        });
    
        Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                //事务操作
            }
        });
    }
    
    

    TransactionTemplate会捕捉TransactionCallback或者TransactionCallbackWithoutResult中抛出的unchecked Exception并回滚事务,然后将异常抛给上层处理。

    如果事务没有任何问题,TransactionTemplate会为我们提交事务,唯一需要我们干预的就是某些情况下的回滚了。如果在操作过程中需要让事务回滚而不是最终提交,一般有两种处理方式:

    • 事务抛出了unchecked exception,TransactionTemplate会自动回滚,如果为checked Exception,则需要在callback内部捕获,然后转义为unchecked exception抛出。

      Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
          protected void doInTransactionWithoutResult(TransactionStatus status) {
              try{
                  //事务操作
              }catch(CheckedException e){
                  throw new RuntimeException(e);
              }
          }
      });
      
    • 使用Callback接口公开的TransactionStatus将事务标记为 rollBackOnly。在TransactionTemplate最终提交事务时,检测到rollBackOnly被设置,则将提交事务更改为回滚事务。

      Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
          protected void doInTransactionWithoutResult(TransactionStatus status) {
            boolean needRoleBack = false;
              //事务操作
              if(needRoleBack)
                  status.setRollbackOnly();
          }
      });
      

    对于事务中可能抛出checked Exception,既想回滚,又想以unchecked exception方式向上传播,可以在捕捉代码中设置 status.setRollbackOnly();

    Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try{
                //事务操作
            }catch(CheckedException e){
                //日志记录不能忽略,事务进行回滚,但是日志中如果没有任何异常信息。
                logger.warn("transaction is rolled back!" + e); 
                status.setRollbackOnly();
            }
        }
    });
    

    上面都是伪代码,下面代码贴上可运行的代码

    @Test
    public void testTransactionTemplate(){
        jdbcTemplate = new JdbcTemplate(dataSource);
        int i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
        System.out.println("表中记录总数:"+i);
    
        //构造函数初始化TransactionTemplate
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    
        template.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                jdbcTemplate.update(INSERT_SQL, "1");
            }
        });
        i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
        System.out.println("表中记录总数:"+i);
    }
    

    声明式事务管理

    直接使用编程式事务管理的不足就是,需要将事务管理代码和业务逻辑代码写在一起。而声明式事务管理则可以避免这一点。声明式事务基于Spring AOP,我们只需要关注事务的横切关注点,所以我们完全可以为其提供相应的Advice实现,然后织入到系统中需要该横切逻辑的Joinpoint处。

    我们只需要提供一个拦截器,在业务方法开始之前开启一个事务,当方法执行完成或异常退出的时候提交或者回滚事务。

    public class PrototypeTransactionInterceptor implements MethodInterceptor {
    
        private PlatformTransactionManager txTransactionManager;
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Method method = invocation.getMethod();
            TransactionDefinition def = getTransactionDefinitionByMethod(method);   
            TransactionStatus status = txTransactionManager.getTransaction(def);
            Object result = null;
            try{
                result = invocation.proceed();
            }catch (Throwable t){
                if(needRollbackOn(t)){      
                    txTransactionManager.rollback(status);
                }else{
                    txTransactionManager.commit(status);
                }
            }
            return null;
        }
        //根据方法获取def
        TransactionDefinition getTransactionDefinitionByMethod(Method method){
            return null;
        }
        //是否需要回滚
        boolean needRollbackOn(Throwable t){
            return false;
        }
    }
    

    另外,还需要知道业务方法是否需要事务,和当抛出异常时如何处理,是否需要回滚。一般来说,可以写死在拦截器中,更好的办法是将这些配置信息放到XML文件中或Java注解的形式标注。

    Spring提供了声明式事务管理的类TransactionInterceptor来代替上面我们自己定义的拦截器,所以,我们只需要决定使用XML方式还是注解的方式即可。

    XML

    Spring2.x以后提供基于XML Schema的配置方式,专门为事务管理提供一个单独的命名空间tx用于简化配置,底层使用的还是TransactionInterceptor

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context
                               http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/tx
                               http://www.springframework.org/schema/tx/spring-tx.xsd
                               http://www.springframework.org/schema/aop
                               http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <context:component-scan base-package="com.example"/>
        <context:property-placeholder location="classpath:spring/db.properties"/>
    
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
            <property name="user" value="root"/>
            <property name="password" value="root"/>
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <!--连接池中保留的最小连接数。 -->
            <property name="minPoolSize">
                <value>5</value>
            </property>
        </bean>
    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- 底层依赖自动代理机制 -->
        <aop:config>
            <aop:pointcut expression="execution(* com.example.*.service.impl.*.*(..))" id="pointcut"/>
            <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
        </aop:config>
    
        <!-- 通过tx指定的事务信息,需要Spring AOP的支持才能织入具体业务对象 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 
                    name:需要事务管理的方法名称,可以使用*通配符
                    propagation:事务传播行为
                    isolution:事务的隔离度
                    timeout:事务超时时间
                    read-only:指定事务是否只读
                    rollback-for:指定触发事务回滚的异常类型
                    no-rollback-for:指定不触发事务回滚的异常类型
                -->
                <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="-1" read-only="false" no-rollback-for="" rollback-for=""/>
                <tx:method name="*" propagation="SUPPORTS"/>
            </tx:attributes>
        </tx:advice>
    
    </beans>
    

    Java注解

    Spring定义了Transactional用于标注业务方法对应的事务,可以直接在注解标注到业务方法或业务方法所在的对象定义上。通过Transactional,可以指定与<tx:advice/>几乎相同的信息。

    下面是Transactional的源码:

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
        @AliasFor("transactionManager")
        String value() default "";
        @AliasFor("value")
        String transactionManager() default "";
        Propagation propagation() default Propagation.REQUIRED;
        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 {};
    }
    

    Java注解具体使用如下,一般推荐在实现类上使用@Transactional,是因为CGLIB代理会遇到问题,因为它是基于类的代理,而不是基于接口。

    @Service
    @Transactional
    public class UserService {
        @Transactional(propagation = Propagation.REQUIRED,readOnly = false,
                timeout = 20,isolation = Isolation.DEFAULT)
        public void printUser(){
            System.out.println("user print");
        }
    }
    

    只使用@Transactional标注并不会为方法带来事务的管理,因为它只是一个标志,需要在执行业务方法的时候,通过反射读取这些信息,并根据这些信息构建事务,这些底层逻辑不需要我们去写,通过在容器中指定下面这句配置即可,所有的工作交给Spring IOC容器搞定。

    <tx:annotation-driven transaction-manager="transactionManager"/>
    

    写在最后

    本来没想写这么多的,最开始只想解决一个开发过程中调遇到的事务问题:在同一个业务类中,一个方法中调用另一个方法,没有开启新事务。

    现在就很清晰了,原因是业务方法中的事务是IOC容器在方法执行前后去织入的,手动调用相当于没有走IOC容器,解决办法有多种,可以将业务方法写在其他业务类中,然后通过注入的方式调用;还可以从IOC容器中获取当前业务类对象,然后调用其方法,但是这种方式需要在配置中暴漏代理对象。

    <aop:aspectj-autoproxy expose-proxy="true"/>
    
    public void printUser(){
        System.out.println("user print");
        ((UserService)AopContext.currentProxy()).c();
    }
    
    public void c(){}
    

    还可以通过注解去设置暴漏代理对象,翻了下源码,在4.2.5中没有该配置,在5.x版本中可以。

    @Configuration
    @EnableAspectJAutoProxy(exposeProxy = true)
    public class AppConfig {
    }
    

    相关文章

      网友评论

        本文标题:Spring事务篇

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