Web 安全实战宝典含源码ppt

#1

download:Web 安全实战宝典含源码ppt

Spring5源码 事务的创立、回滚、提交

1. 前言

1.1 TransactionSynchronizationManager

TransactionSynchronizationManager 中运用 ThreadLocal 保管了在不同线程中不同事务的信息。

public abstract class TransactionSynchronizationManager {
	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");
	private static final ThreadLocal<Set> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");
	private static final ThreadLocal currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");
	private static final ThreadLocal currentTransactionReadOnly =
			new NamedThreadLocal<>("Current transaction read-only status");
	private static final ThreadLocal currentTransactionIsolationLevel =
			new NamedThreadLocal<>("Current transaction isolation level");
	private static final ThreadLocal actualTransactionActive =
			new NamedThreadLocal<>("Actual transaction active");
	...
}
复制代码

我们从上面的局部代码能够看到, TransactionSynchronizationManager 中保管的是各个线程中的事务信息。

1.2 事务属性

1.2.1 只读

对一个查询操作来说,假如我们把它设置成 只读 ,就可以明白通知数据库,这个操作不触及写操作。这 样数据库就可以针对查询操作来停止优化。

Transactional(readOnly = true)
复制代码

留意: 对增删改操作设置只读会抛出下面异常Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

1.2.2 超时

事务在执行过程中,有可能由于遇到某些问题,招致程序卡住,从而长时间占用数据库资源。而长时间 占用资源,大约率是由于程序运转呈现了问题(可能是Java程序或MySQL数据库或网络衔接等等)。 此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务完毕,把资源让出来,让其他正常 程序能够执行。概括来说就是一句话: 超时回滚,释放资源

执行过程中抛出异常: org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

1.2.3 回滚战略

声明式事务 默许只针对运转时异常回滚,编译时异常不回滚 。能够经过@Transactional中相关属性设置回滚战略:

  • rollbackFor属性 :需求设置一个Class类型的对象
  • rollbackForClassName属性 :需求设置一个字符串类型的全类名
  • noRollbackFor属性 :需求设置一个Class类型的对象
  • noRollbackForClassName属性 :需求设置一个字符串类型的全类名


### 1.2.4 事务隔离级别

数据库系统必需具有隔离并发运转各个事务的才能,使它们不会互相影响,防止各种并提问题。一个事 务与其他事务隔离的水平称为隔离级别。SQL规范中规则了多种事务隔离级别,不同隔离级别对应不同 的干扰水平,隔离级别越高,数据分歧性就越好,但并发性越弱。隔离级别一共有四种:

1.2.5 事务传播行为

当事务办法被另一个事务办法调用时,必需指定事务应该如何传播。例如: 办法可能继续在现有事务中运转,也可能开启一个新事务,并在本人的事务中运转

事务传播属性 解释
PROPAGATION_REQUIRED 支持当前事务,假如当前没有事务,就新建一个事务。这是最常见的选择。即假如上级具有事务,则运用上级的事务,不具备则本人新建一个事务
PROPAGATION_REQUIRES_NEW 新建事务,假如当前存在事务,把当前事务挂起。即假如上级存在事务,则挂起上级事务,运用本人新创立的事务
PROPAGATION_MANDATORY 支持当前事务,假如当前没有事务,就抛出异常。即假如上级具有事务,则运用上级的事务,上级没有事务,则抛出异常
PROPAGATION_SUPPORTS 支持当前事务,假如当前没有事务,就以非事务方式执行。即假如上级具有事务,则运用上级的事务,假如上级没有事务,则不开启事务
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,假如当前存在事务,就把当前事务挂起。即假如上级具有事务,则运用挂起上级事务,运用非事务方式。
PROPAGATION_NEVER 以非事务方式执行,假如当前存在事务,则抛出异常
PROPAGATION_NESTED 假如当前存在事务,则在嵌套事务内执行。假如当前没有事务,则停止与PROPAGATION_REQUIRED相似的操作。

这里解释一下 PROPAGATION_NESTEDPROPAGATION_REQUIRES_NEW 的区别:

  • PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完整 commited 或 rolledback 而不依赖于外部事务,它具有本人的隔离范围, 本人的锁, 等等. 当内部事务开端执行时, 外部事务将被挂起, 内部事务完毕时, 外部事务将继续执行。 PROPAGATION_REQUIRES_NEW 常用于日志记载,或者买卖失败仍需求留痕
  • PROPAGATION_NESTED 开端一个 “嵌套的” 事务, 它是曾经存在事务的一个真正的子事务. 潜套事务开端执行时, 它将获得一个 savepoint.假如这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一局部,只要外部事务完毕后它才会被提交.

由此可见, PROPAGATION_REQUIRES_NEWPROPAGATION_NESTED 的最大区别在于:

  • PROPAGATION_REQUIRES_NEW 完整是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 假如外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 rollback.

2. 事务的创立 - createTransactionIfNecessary

上篇中,我们剖析到 TransactionAspectSupport#invokeWithinTransaction 办法完成了事务的加强调用。而其中 createTransactionIfNecessary 办法则是在需求的时分创立了事务,之所以说需求的时分而不是说直接创立,是由于这里要思索到事务的传播属性。

createTransactionIfNecessary 的完成是在 TransactionAspectSupport#createTransactionIfNecessary 中,完成了事务的创立,这里面思索了事务的传播属性的处置,所以并不是一定会创立事务,依据传播属性的不同会有不同的处置。

doBegin(transaction, definition);

doBegin(transaction, definition); 的完成在 DataSourceTransactionManager#doBegin 中。 这里的目的是 为了结构 transaction ,包括设置 ConnectionHolder 、隔离级别、timeout。假如是新衔接,则绑定到当前线程。

protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;
        try {	
                // 假如当前线程中的数据库衔接不存在,或者事务同步为true的状况下需求重新获取数据库衔接,停止同步
                if (!txObject.hasConnectionHolder() ||
                                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                        Connection newCon = obtainDataSource().getConnection();
                        if (logger.isDebugEnabled()) {
                                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                        }
                        txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
                }
                // 将同步标识设置为 true
                txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
                con = txObject.getConnectionHolder().getConnection();
                // 设置事务隔离级别
                Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
                txObject.setPreviousIsolationLevel(previousIsolationLevel);
                txObject.setReadOnly(definition.isReadOnly());
                // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
                // so we don't want to do it unnecessarily (for example if we've explicitly
                // configured the connection pool to set it already).
                // 更改自动提交,将数据库的自动提交改为 Spring 来控制,否则数据库执行完毕sql后自动提交了
                if (con.getAutoCommit()) {
                        txObject.setMustRestoreAutoCommit(true);
                        if (logger.isDebugEnabled()) {
                                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                        }
                        con.setAutoCommit(false);
                }
                // 准备事务衔接,这里实践上执行了 SET TRANSACTION READ ONLY 的sql 语句
                prepareTransactionalConnection(con, definition);
                // 设置当前线程曾经存在事务,这个 transactionActive 属性是判别能否当前线程存在事务的根据
                txObject.getConnectionHolder().setTransactionActive(true);
                // 设置超时时间
                int timeout = determineTimeout(definition);
                if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                        txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
                }
                // Bind the connection holder to the thread.
                // 假如是新的衔接,则绑定数据库衔接到当前线程
                if (txObject.isNewConnectionHolder()) {
                        TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
                }
        }
        catch (Throwable ex) {
                if (txObject.isNewConnectionHolder()) {
                        DataSourceUtils.releaseConnection(con, obtainDataSource());
                        txObject.setConnectionHolder(null, false);
                }
                throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
        }
}
...
protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)
                throws SQLException {
        if (isEnforceReadOnly() && definition.isReadOnly()) {
                try (Statement stmt = con.createStatement()) {
                        // 设置事务为只读
                        stmt.executeUpdate("SET TRANSACTION READ ONLY");
                }
        }
}
复制代码

能够说事务是从这个函数开端的,由于在这个函数中曾经开端尝试对数据库衔接的获取,并在在获取数据库衔接的同时,也停止了一些必要的设置的同步。

  • 尝试获取衔接。但并不是每一次都会获取新的衔接,假如当前线程中的 ConnectionHolder 曾经存在,则没必要再次获取,或者对事物同步表示设置为true的需求重新获取衔接。
  • 设置隔离级别和只读标识
  • 更改事务的默许提交设置。
  • 假如事务属性是自动提交,那么需求改动这个设置,将操作拜托给Spring来处置。
  • 设置标志位,标识当前衔接曾经被事务激活。
  • 设置过时时间
  • 将ConnectionHolder 绑定到当前线程

我们这里额外看一下 DataSourceUtils.prepareConnectionForTransaction 办法是如何设置隔离级别的

public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
                throws SQLException {
        Assert.notNull(con, "No Connection specified");
        boolean debugEnabled = logger.isDebugEnabled();
        // Set read-only flag.
        // 设置属性只读
        if (definition != null && definition.isReadOnly()) {
                try {
                        if (debugEnabled) {
                                logger.debug("Setting JDBC Connection [" + con + "] read-only");
                        }
                        con.setReadOnly(true);
                }
                catch (SQLException | RuntimeException ex) {
                        Throwable exToCheck = ex;
                        while (exToCheck != null) {
                                if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
                                        // Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0
                                        throw ex;
                                }
                                exToCheck = exToCheck.getCause();
                        }
                        // "read-only not supported" SQLException -> ignore, it's just a hint anyway
                        logger.debug("Could not set JDBC Connection read-only", ex);
                }
        }
        // Apply specific isolation level, if any.
        // 设置数据库隔离级别
        Integer previousIsolationLevel = null;
        if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
                if (debugEnabled) {
                        logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
                                        definition.getIsolationLevel());
                }
                int currentIsolation = con.getTransactionIsolation();
                if (currentIsolation != definition.getIsolationLevel()) {
                        previousIsolationLevel = currentIsolation;
                        con.setTransactionIsolation(definition.getIsolationLevel());
                }
        }
        return previousIsolationLevel;
}
复制代码

能够看到,在 DataSourceUtils.prepareConnectionForTransaction 办法中并没有什么复杂的逻辑,由于其主要完成都交由更底层的 数据库API 来完成了。

prepareSynchronization(status, definition);

prepareSynchronization(status, definition); 的完成在 AbstractPlatformTransactionManager#prepareSynchronization 中。其目的是 将事务信息记载到当前线程中 ,逻辑很简单,这里不再赘述。

protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
        // 假如是新的事务,则需求同步信息
        if (status.isNewSynchronization()) {
                // 下面是对事务的信息的记载,记载到当前线程中。
                TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
                TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
                                definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
                                                definition.getIsolationLevel() : null);
                TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
                TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
                TransactionSynchronizationManager.initSynchronization();
        }
}
复制代码

2.2 构建事务信息 - prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);

prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); 的完成是在 TransactionAspectSupport#prepareTransactionInfo 中。

当曾经树立事务衔接并完成了事务信息的提取后,我们需求将一切的事务信息统一记载在 TransactionInfo 类型的实例中,这个实例包含了目的办法开端前的一切状态信息,一旦事务执行失败,Spring会经过 TransactionInfo 类实例中的信息来停止回滚等后续工作。

细致代码如下:

protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
                @Nullable TransactionAttribute txAttr, String joinpointIdentification,
                @Nullable TransactionStatus status) {
        // 封装成 TransactionInfo  实例
        TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
        if (txAttr != null) {
                // 记载事务状态
                txInfo.newTransactionStatus(status);
        }
        else {
        }
        // 绑定到线程当前上
        txInfo.bindToThread();
        return txInfo;
}
复制代码

2.3 总结

TransactionAspectSupport#createTransactionIfNecessary 的功用是依据需求创立事务。这里面思索到嵌套事务的状况,并对事务的传播属性停止了相应的处置,最终处置后。返回的是一个 TransactionInfo 的值,里面封装了事务的各种信息,供应后面的回滚或者提交运用。

3. 事务的回滚 - completeTransactionAfterThrowing

completeTransactionAfterThrowing 的完成是 TransactionAspectSupport#completeTransactionAfterThrowing 。在呈现异常的时分,经过该办法停止事务的回滚。

下面我们来看详细代码完成:

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
        // 判别当前线程中能否存在事务
        if (txInfo != null && txInfo.getTransactionStatus() != null) {
                if (logger.isTraceEnabled()) {
                        logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                                        "] after exception: " + ex);
                }
                // 判别能否触发回滚操作,这里的条件是异常能否是 RuntimeException 或 error类型
                // 即 return (ex instanceof RuntimeException || ex instanceof Error);
                if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                        try {
                                // 执行回滚操作
                                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                        }
                        catch (TransactionSystemException ex2) {
                                logger.error("Application exception overridden by rollback exception", ex);
                                ex2.initApplicationException(ex);
                                throw ex2;
                        }
                        catch (RuntimeException | Error ex2) {
                                logger.error("Application exception overridden by rollback exception", ex);
                                throw ex2;
                        }
                }
                else {
                        // We don't roll back on this exception.
                        // Will still roll back if TransactionStatus.isRollbackOnly() is true.
                        try {
                                // 假如不满足回滚条件,则还是会提交,也就是说假如抛出异常不是 RuntimeException 或 error类型,则不会触发事务的回滚。
                                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                        }
                        catch (TransactionSystemException ex2) {
                                logger.error("Application exception overridden by commit exception", ex);
                                ex2.initApplicationException(ex);
                                throw ex2;
                        }
                        catch (RuntimeException | Error ex2) {
                                logger.error("Application exception overridden by commit exception", ex);
                                throw ex2;
                        }
                }
        }
}
复制代码

上面能够看到,触发回滚操作的判别条件是

txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)
复制代码

txInfo.transactionAttribute != null 自不用说,表示必需存在事务属性信息,即是事务办法。 txInfo.transactionAttribute.rollbackOn(ex) 默许的完成如下:

public boolean rollbackOn(Throwable ex) {
        return (ex instanceof RuntimeException || ex instanceof Error);
}
复制代码

也就是说只要当异常类型是 Error或者 RuntimeException 才会停止回滚。 这里需求留意, 假如抛出了其他类型异常,那么并不代表事务没有启用,而是回滚没有触发

txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 回滚的详细操作即即 中的 AbstractPlatformTransactionManager#rollback 办法

@Override
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, false);
}
....
// 省略了日志打印
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
        try {
                boolean unexpectedRollback = unexpected;
                try {
                        // 激活一切 TransactionSynchronization 中对应的办法 beforeCompletion() 办法
                        triggerBeforeCompletion(status);
                        // 假如当前事务有保管点,也就是当前事务为单独的线程则会退到保管点
                        if (status.hasSavepoint()) {
                                status.rollbackToHeldSavepoint();
                        }
                        else if (status.isNewTransaction()) {
                                // 假如当前事务为独立的新事物,则直接回退
                                doRollback(status);
                        }
                        else {
                                // Participating in larger transaction
                                if (status.hasTransaction()) {
                                        if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                                                // 假如当前事务不是独立的事务,那么只能标志状态,等到事务链执行终了后统一回滚
                                                doSetRollbackOnly(status);
                                        }
                                        else {
                                        }
                                }
                                else {
                                }
                                // Unexpected rollback only matters here if we're asked to fail early
                                if (!isFailEarlyOnGlobalRollbackOnly()) {
                                        unexpectedRollback = false;
                                }
                        }
                }
                catch (RuntimeException | Error ex) {
                        triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                        throw ex;
                }
                // 激活一切 TransactionSynchronization 中对应的办法 afterCompletion() 办法
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
                // Raise UnexpectedRollbackException if we had a global rollback-only marker
                if (unexpectedRollback) {
                        throw new UnexpectedRollbackException(
                                        "Transaction rolled back because it has been marked as rollback-only");
                }
        }
        finally {
                // 清空记载的资源并将挂起的资源恢复
                cleanupAfterCompletion(status);
        }
}
复制代码

我们能够简单总结一下整个脉络:

  1. 首先是 自定义触发器 的调用,这里是Spring提供的回滚扩展点,包括在回滚前、回滚胜利、回滚失败的触发器调用,自定义触发器会依据这些信息作出进一步的处置。而关于触发器的注册,常见的是在回调过程中经过 TransactionSynchronizationManager#registerSynchronization 办法完成,
  2. 除了触发监听函数外,就是真正的回滚逻辑处置了,而这里的详细回滚操作都拜托给了底层数据库衔接提供的API 来操作的。回滚的根本战略是:
  • 当之前曾经保管的事务信息中有保管点信息的时分,运用保管点信息回滚。常用于嵌入式事务,关于嵌入式事务的处置,内嵌的事务异常并不会惹起外部事物的回滚。
  • 关于之前没有保管的事务信息中的事务为新事物,那么直接回滚。常用语单独事务的处置,关于没有保管点的回滚,Spring同样是运用底层数据库衔接提供的API 来操作的。我们这里则是 DataSourceTransactionManager#doRollback
  • 当前事务信息标明是存在事务的,又不属于以上两种状况,多数用于 JTA,只做回滚标识,等到提交的时分统一不提交。
  1. 回滚后的信息肃清。关于回滚逻辑执行完毕后,无论回滚能否胜利,都必需要将信息肃清。

3.1 自定义触发器的调用

自定义触发器的调用,包括在 回滚前、回滚胜利、回滚失败的触发器调用 。完成逻辑都相似,我们这里挑一个回滚前的触发器看一看逻辑:

protected final void triggerBeforeCompletion(DefaultTransactionStatus status) {
        if (status.isNewSynchronization()) {
                if (status.isDebug()) {
                        logger.trace("Triggering beforeCompletion synchronization");
                }
                // 触发前置操作
                TransactionSynchronizationUtils.triggerBeforeCompletion();
        }
}
复制代码

TransactionSynchronizationUtils.triggerBeforeCompletion(); 的代码如下:

public static void triggerBeforeCompletion() {
        // 获取一切TransactionSynchronization 触发 beforeCompletion 办法
        for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
                try {
                        synchronization.beforeCompletion();
                }
                catch (Throwable tsex) {
                        logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex);
                }
        }
}
复制代码

triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 与之逻辑相似,这里就不再看了。

3.2 回滚逻辑处置

回滚操作最底层的操作都是拜托给了数据库API 来完成 ,所以这里并没有担任的逻辑。

回滚逻辑上面也说了有三种状况,下面我们来一个一个看一看:

3.2.1. status.rollbackToHeldSavepoint();

status.rollbackToHeldSavepoint(); 的完成在 AbstractTransactionStatus#rollbackToHeldSavepoint 中。

处置状况是 :当之前曾经保管的事务信息中有保管点信息的时分,运用保管点信息回滚。常用于嵌入式事务,关于嵌入式事务(并非是嵌套service事务)的处置,内嵌的事务异常并不会惹起外部事物的回滚。

public void rollbackToHeldSavepoint() throws TransactionException {
        Object savepoint = getSavepoint();
        if (savepoint == null) {
                throw new TransactionUsageException(
                                "Cannot roll back to savepoint - no savepoint associated with current transaction");
        }
        // 回滚到 savepoint 
        getSavepointManager().rollbackToSavepoint(savepoint);
        // 释放保管点
        getSavepointManager().releaseSavepoint(savepoint);
        setSavepoint(null);
}
复制代码

这里运用的是 JDBC 的方式停止数据库了衔接,所以这里调用的是 getSavepoint() ; 返回的是 JdbcTransactionObjectSupport 类型。所以这里我们来看 JdbcTransactionObjectSupport 中的

@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
        ConnectionHolder conHolder = getConnectionHolderForSavepoint();
        try {
                // 直接调用 Connection的rollback 办法
                conHolder.getConnection().rollback((Savepoint) savepoint);
                conHolder.resetRollbackOnly();
        }
        catch (Throwable ex) {
                throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
        }
}
@Override
public void releaseSavepoint(Object savepoint) throws TransactionException {
        ConnectionHolder conHolder = getConnectionHolderForSavepoint();
        try {
                // 直接调用 Connection的releaseSavepoint办法
                conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);
        }
        catch (Throwable ex) {
                logger.debug("Could not explicitly release JDBC savepoint", ex);
        }
}
复制代码

3.2.2. doRollback(status);

doRollback(status); 的完成在 DataSourceTransactionManager#doRollback 中。

处置状况是 :关于之前没有保管的事务信息中的事务为新事物,那么直接回滚。常用语单独事务的处置,关于没有保管点的回滚,Spring同样是运用底层数据库衔接提供的API 来操作的。

@Override
protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {
                logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }
        try {
                // 直接调用rollback办法 回滚
                con.rollback();
        }
        catch (SQLException ex) {
                throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
        }
}
复制代码

3.2.3. doSetRollbackOnly(status);

doSetRollbackOnly(status); 的完成在 DataSourceTransactionManager#doSetRollbackOnly 中。

处置状况: 当前事务信息标明是存在事务的,又不属于以上两种状况,多数用于 JTA(Java Transaction API),只做回滚标识,等到提交的时分统一不提交。即当外围事务停止提交时,发现内嵌的事务中 rollbackOnly 被设置成true。则直接停止回滚,而不再尝试提交

@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        if (status.isDebug()) {
                logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
                                "] rollback-only");
        }
        txObject.setRollbackOnly();
}
复制代码

3.3 回滚信息肃清

cleanupAfterCompletion(status); 的完成在 AbstractPlatformTransactionManager#cleanupAfterCompletion 中。 其作用是完成事务回滚的收尾工作,主要包括以下内容 :

  • 设置状态是对事务信息做完成标识以防止反复调用
  • 假如当前事务是新的同步状态,需求将绑定到当前线程的事务信息肃清
  • 假如是新事物需求做出肃清资源的工作
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
        status.setCompleted();
        if (status.isNewSynchronization()) {
                // 肃清当前线程的中关于该事务的信息
                TransactionSynchronizationManager.clear();
        }
        if (status.isNewTransaction()) {
                // 清算事务信息
                doCleanupAfterCompletion(status.getTransaction());
        }
        if (status.getSuspendedResources() != null) {
                if (status.isDebug()) {
                        logger.debug("Resuming suspended transaction after completion of inner transaction");
                }
                Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
                // 完毕之前事务的挂起状态。
                resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
        }
}
复制代码

我们来看看下面的两个办法:

3.3.1. doCleanupAfterCompletion(status.getTransaction());

doCleanupAfterCompletion(status.getTransaction()); 的完成在 DataSourceTransactionManager#doCleanupAfterCompletion 中。在这里完成事务回滚的收尾工作。

@Override
protected void doCleanupAfterCompletion(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        // Remove the connection holder from the thread, if exposed.
        if (txObject.isNewConnectionHolder()) {
                // 将数据库衔接从当前线程中解除绑定
                TransactionSynchronizationManager.unbindResource(obtainDataSource());
        }
        // Reset connection.
        // 释放衔接
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
                if (txObject.isMustRestoreAutoCommit()) {
                        // 恢复数据库衔接自动提交属性
                        con.setAutoCommit(true);
                }
                // 重置数据衔接
                DataSourceUtils.resetConnectionAfterTransaction(
                                con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
        }
        catch (Throwable ex) {
                logger.debug("Could not reset JDBC Connection after transaction", ex);
        }
        if (txObject.isNewConnectionHolder()) {
                if (logger.isDebugEnabled()) {
                        logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
                }
                // 假如当前事务是独立的新创立的事务则在事务完成时释放数据库衔接。
                DataSourceUtils.releaseConnection(con, this.dataSource);
        }
        txObject.getConnectionHolder().clear();
}
复制代码

3.3.2. resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());

假如在事务执行前有事务挂起,那么当前事务执行完毕后需求将挂起的事务恢复。

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
                throws TransactionException {
        if (resourcesHolder != null) {
                // 获取挂起的事务
                Object suspendedResources = resourcesHolder.suspendedResources;
                if (suspendedResources != null) {
                        // 恢复事务
                        doResume(transaction, suspendedResources);
                }
                List suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
                // 设置事务属性
                if (suspendedSynchronizations != null) {
                        TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
                        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
                        TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
                        TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
                        doResumeSynchronization(suspendedSynchronizations);
                }
        }
}
复制代码

从上面整个回滚流程能够看到,很多的操作,Spring都交给数据库底层完成了,Spring仅仅起到了一个管理的作用。

3.4 总结

事务的回滚操作,依据不同的状况执行不同的回滚战略。并且在回滚完毕之后恢复了挂起的事务(假如有挂起事务)。

4. 事务的提交 - commitTransactionAfterReturning

commitTransactionAfterReturning 的完成在 TransactionAspectSupport#commitTransactionAfterReturning 中。完成了事务的提交任务。

上面我们剖析了Spring的事务异常处置机制。假如事务的执行并没有呈现任何异常,那么也就意味着事务能够走正常的提交流程了。

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
        if (txInfo != null && txInfo.getTransactionStatus() != null) {
                if (logger.isTraceEnabled()) {
                        logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
                }
                // 停止事务提交
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
}
复制代码

在真正的数据提交前,还需求做一个判别,在上面剖析事务异常处置规则的时分,当某个事务既没有保管点也不是新事物的时分,Spring对他的处置方式只是设置一个回滚标识(在事务回滚的第三种回滚战略中完成 )。这个回滚标识在这里就会派上用场了。应用场景如下:

                    throw ex;
            }
            catch (RuntimeException | Error ex) {
                    if (!beforeCompletionInvoked) {
                            triggerBeforeCompletion(status);
                    }
                    // 提交过程中会呈现异常则回滚
                    doRollbackOnCommitException(status, ex);
                    throw ex;
            }
            // 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 {
            // 清算事务信息
            cleanupAfterCompletion(status);
    }

}
复制代码


`DataSourceTransactionManager#doCommit`  中的完成也就是至极调用数据库衔接的  `commit`  办法。

protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug(“Committing JDBC transaction on Connection [” + con + “]”);
}
try {
// 调用commit 办法
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException(“Could not commit JDBC transaction”, ex);
}
}
复制代码


## 4.3 总结

事务的提交并不是直接提交,也是有几个方面的思索:

* 假如事务被设置了回滚标识,则不会提交,直接回滚
* 假如事务中有保管点信息则不会提交事务操作。
* 假如事务非新事务的时分也不会去执行提交事务操作。