Spring事务篇

写在最前

这些天看了一些关于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 {
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring 事务属性分析 事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的...
    壹点零阅读 1,295评论 0 2
  • 事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性:...
    JAVA开发之路阅读 389评论 0 0
  • 事务: 事务是逻辑上的一组操作,要么都执行,要么都不执行。 事物的特性:(ACID) 原子性: 事务是最小的执行单...
    n油炸小朋友阅读 431评论 1 1
  • 例如新兵连SPIN法则 S现状问题:新人微商小白,什么都不会也不懂。 P背后问题:...
    f芬姐阅读 579评论 0 0
  • 杜鹃花未谢 杜鹃鸟的叫声在我心空颤颤走过 雨凉嗖嗖的,如我 默然无语 与兄长、妹妹在您墓碑前 身体弯成弓的模样 模...
    桂山花石草庐阅读 210评论 0 4