【重写SpringFramework】条件判定(chapter 4-4)

1. 前言

上一节介绍了 Spring 事务的基本原理,引入横向结构保证了事务方法的独立性,又通过全局回滚标记使得事务方法之间可以通信。现在的问题是,Spring 事务是如何运行的?具体来说,Spring 提供了事务管理器 来对事务进行管理。开发者可以通过该组件来编写事务逻辑,这种方式也称为编程式事务

2. 事务管理器

2.1 概述

事务管理器作为 tx 模块的核心组件,不仅提供了控制事务的底层逻辑,还涉及到事务的传播行为。继承结构比较简单,我们主要关心本地事务的处理。

  • PlatformTransactionManager:顶级接口,定义了事务的操作
  • AbstractPlatformTransactionManager:实现了开启事务、提交事务和回滚事务的主体流程,细节部分由子类完成
  • DataSourceTransactionManager:基于 JDBC 的事务管理器,可以处理本地事务
  • JtaTransactionManager:负责实现全局事务(仅了解)

2.2 PlatformTransactionManager

PlatformTransactionManager 接口定义了操作事务的行为,这三个方法需要相互配合才能实现完成的事务操作。

  • getTransaction:获取事务状态,也就是开启事务
  • commit:提交事务
  • rollback:回滚事务
java 复制代码
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition);
    void commit(TransactionStatus status);
    void rollback(TransactionStatus status);
}

2.3 AbstractPlatformTransactionManager

AbstractPlatformTransactionManager 作为抽象实现类,通过模板方法模式 实现了父接口的三个方法。也就是说,该类只完成了开启事务、提交和回滚事务的基本流程,细节部分由子类来实现 doGetTransaction 等抽象方法。

java 复制代码
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
    //重写父类的具体操作
    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) {}
    @Override
    public void commit(TransactionStatus status) {}
    @Override
    public void rollback(TransactionStatus status) {}

    //抽象模板方法,交给子类的事务对象来处理
    protected abstract Object doGetTransaction();
    protected abstract boolean isExistingTransaction(Object transaction);
    protected abstract void doBegin(Object transaction, TransactionDefinition definition);
    protected abstract Object doSuspend(Object transaction);
    protected abstract void doResume(Object transaction, Object suspendResources);
    protected abstract void doCommit(DefaultTransactionStatus status);
    protected abstract void doRollback(DefaultTransactionStatus status);
    protected abstract void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException;
    protected abstract void doCleanupAfterCompletion(Object transaction);
}

2.4 DataSourceTransactionManager

DataSourceTransactionManager 作为默认的实现类,针对 JDBC 这一特殊场景的具体实现。该类持有一个 dataSource 字段,表示数据源。一个应用中可以存在多个事务管理器,一个事务管理器对应一个数据源

java 复制代码
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
    private DataSource dataSource;

    public DataSourceTransactionManager(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //内部类,事务对象
    private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport {

        public void setRollbackOnly() {
            getConnectionHolder().setRollbackOnly();
        }

        @Override
        public boolean isRollbackOnly() {
            return getConnectionHolder().isRollbackOnly();
        }
    }
}

此外,内部类 DataSourceTransactionObject事务对象 的具体实现,setRollbackOnlyisRollbackOnly 方法说明对 rollbackOnly 的访问是全局性的。关于这一点已经在上节介绍过了。

3. 事务管理

3.1 获取事务

接下来,我们以 AbstractPlatformTransactionManager 为切入点,详细分析事务操作的相关方法,首先来看获取事务的流程。getTransaction 方法涉及到事务的传播行为,为了降低代码的复杂度,我们先讨论最简单的情况,即多个方法属于同一个事务。创建新事务的过程可以分为三步,首先获取事务对象,然后执行开启事务的流程,最后创建 DefaultTransactionStatus 对象并返回。

java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
@Override
public TransactionStatus getTransaction(TransactionDefinition definition) {
    //TODO 涉及事务的传播,先简化处理,后续完善
    //1. 获取事务对象
    Object transaction = doGetTransaction();
    //2. 开启事务
    doBegin(transaction, definition);
    //3. 返回事务状态
    DefaultTransactionStatus status = new DefaultTransactionStatus(transaction, null, true, true, definition.isReadOnly());
    prepareSynchronization(status, definition);
    return status;
}

第一步,获取事务对象。doGetTransaction 是模板方法,由子类 DataSourceTransactionManager 实现。首先创建事务对象的实例,然后尝试获取线程绑定的 Connection,并绑定到事务对象上。第一次执行 doGetTransaction 方法,线程上没有绑定 ConnectionHolder,这时的事务对象实际上是一个空事务。

java 复制代码
//所属类[cn.stimd.spring.jdbc.datasource.DataSourceTransactionManager]
@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    //尝试获取线程绑定的Connection,第一次为null
    ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
    txObject.setConnectionHolder(holder);
    return txObject;
}

第二步,开启事务。DataSourceTransactionManager 实现了 doBegin 方法,可以分为三步:

  1. 获取数据库连接。事务对象上可能没有绑定 Connection,需要从数据源中拿到 Connection,并设置到事务对象上。
  2. 对数据库连接和事务对象的一些设置,包括设置隔离级别,关闭自动提交,设置事务开启的标记,设置超时时间等。
  3. ConnectionHolder 绑定到线程上,此时的事务对象才是真正可用的。
java 复制代码
//所属类[cn.stimd.spring.jdbc.datasource.DataSourceTransactionManager]
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;
    try {
        //1. 获取数据库连接,并绑定到事务对象上
        if(txObject.getConnectionHolder() == null){
            Connection newCon = this.dataSource.getConnection();
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
        con = txObject.getConnectionHolder().getConnection();

        //2. 准备工作
        //设置隔离级别
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        //关闭自动提交
        if(con.getAutoCommit()){
            con.setAutoCommit(false);
        }

        //设置事务开启标记,判定是否事务存在的依据
        txObject.getConnectionHolder().setTransactionActive(true);

        //设置超时
        if(definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT){
            txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
        }

        //3. 将Connection绑定到线程上
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(this.dataSource, txObject.getConnectionHolder());
        }
    }catch (Throwable e){
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.dataSource);
            txObject.setConnectionHolder(null, false);
        }
        throw new TransactionException("Could not open JDBC Connection for transaction", ex);
    }
}

3.2 提交事务

提交事务的复杂之处在于不仅仅只有提交的工作,某些情况下需要进行回滚。在多方法的事务中,假如内层的事务方法出现异常,外层的事务方法正常执行。那么外层的事务方法在执行 commit 方法时,需要对整个事务进行回滚。原因在于,为了保证事务的一致性,内层方法不能直接回滚,只能由外层方法进行统一处理。理解了这一点,我们来看提交操作的具体流程,可以分为三步:

  1. 如果事务已完成(提交或回滚),不能重复执行,抛出异常。
  2. 检查全局回滚标记,如果为 true,则进入回滚的流程。对于外层的事务方法,最后还需要抛出异常,宣告整个事务执行失败。
  3. 如果不需要回滚,调用 processCommit 方法进一步执行提交流程。
java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
@Override
public void commit(TransactionStatus status) {
    //1. 检查事务是否已完成,避免重复执行
    if (status.isCompleted()) {
        throw new IllegalStateException("提交失败,事务已提交或回滚");
    }

    //2. 如果rollbackOnly被标记为true,说明内层事务发生异常,需要回滚
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    if(defStatus.isGlobalRollbackOnly()){
        //转向回滚流程
        processRollback(defStatus);

        //如果是新事务,即最外层事务,需要抛出异常,宣告整个事务执行失败
        if(status.isNewTransaction()){
            throw new TransactionException("事务被标记为rollback-only,发生回滚");
        }
        return;
    }

    //3. 执行提交流程
    processCommit(defStatus);
}

processCommit 方法中,需要考虑两种情况。一是检查是否为新事务,也就是说由开启事务的方法来提交事务,确保该方法和之后调用的方法在一个事务里。DataSourceTransactionManager 实现了真正的提交操作,在 doCommit 方法中,先拿到事务对象,进一步获取数据库连接,然后执行数据库连接的 commit 方法即可。

java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
private void processCommit(DefaultTransactionStatus status) {
    try {
        try {
            //只有新事务,才执行提交操作
            if(status.isNewTransaction()){
                doCommit(status);
            }
        } catch (RuntimeException | Error ex) {
            //出现异常,触发回滚
            doRollbackOnCommitException(status);
            throw ex;
        }
    } finally {
        //清理操作
        cleanupAfterCompletion(status);
    }
}

//所属类[cn.stimd.spring.jdbc.datasource.DataSourceTransactionManager]
@Override
protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        con.commit();
    } catch (SQLException e) {
        throw new TransactionException("提交事务失败");
    }
}

此外,提交过程中可能出现异常,需要转入回滚流程。doRollbackOnCommitException 方法处理了两种情况,内层的事务方法仅标记全局回滚,外层事务方法才执行真正的回滚操作。

java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
//回滚在提交时发生的异常
private void doRollbackOnCommitException(DefaultTransactionStatus status) throws TransactionException {
    //新事务,执行回滚操作
    if (status.isNewTransaction()) {
        doRollback(status);
    }
    //内层事务,将rollbackOnly标记设置为true
    else if (status.hasTransaction()) {
        doSetRollbackOnly(status);
    }
}

3.3 回滚事务

先来看 rollback 方法。首先检查事务的状态是否标记为已完成,如果是则说明事务已提交或关闭,不能重复执行,抛出异常。接下来进入 processRollback 方法,对事务的处理可以分为两种情况:

  1. 如果是新事务,执行真正的回滚操作
  2. 不是新事务,且事务对象存在,说明是内层事务,只需要将全局回滚标记设置为 true
java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
@Override
public void rollback(TransactionStatus status) {
    if (status.isCompleted()) {
        throw new IllegalStateException("回滚失败,事务已提交或回滚");
    }
    processRollback((DefaultTransactionStatus) status);
}

//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
private void processRollback(DefaultTransactionStatus status) {
    try {
        //1. 只有新事务,才执行真正的回滚操作
        if (status.isNewTransaction()) {
            doRollback(status);
        }
        //2. 内层事务,将rollbackOnly标记设置为true
        else if(status.hasTransaction()){
            doSetRollbackOnly(status);
        }
    } finally {
        //清理操作
        cleanupAfterCompletion(status);
    }
}

DataSourceTransactionManager 实现了真正的回滚操作,doRollback 方法的实现比较简单,先获取事务对象,然后获取数据库连接对象,最后调用数据库连接的 rollback 方法即可。

java 复制代码
//所属类[cn.stimd.spring.jdbc.datasource.DataSourceTransactionManager]
@Override
protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        con.rollback();
    } catch (SQLException e) {
        throw new TransactionException("回滚事务失败");
    }
}

3.4 事务完成的清理工作

提交和回滚都被视为事务完成,都需要进行收尾工作,因此都会调用 cleanupAfterCompletion 方法。事务的清理工作可以分为四步,如下所示:

  • 首先将 completed 标记为 true,避免重复执行提交或回滚操作。
  • 如果是新的同步,清除线程绑定的相关资源(仅了解即可)
  • 如果是新的事务,则由 doCleanupAfterCompletion 方法来处理
  • 恢复先前挂起的事务。(待实现)

注:挂起(suspend)和恢复(resume)是一组操作,彼此不可分割。前边提到,开启事务是一个非常复杂的操作,目前只讨论最简单的情况,因此省略了挂起的操作。同样地,暂时略过 resume 方法的实现。具体细节将在「事务的传播行为」一节中介绍。

java 复制代码
//所属类[cn.stimd.spring.transaction.support.AbstractPlatformTransactionManager]
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    //将completed属性设置为true
    status.setCompleted();

    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }

    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }

    if (status.getSuspendedResources() != null) {
        resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

接下来我们来看 doCleanupAfterCompletion 方法的具体实现,主要由四个步骤组成:

  • 解除线程绑定的数据源,这里的资源实际上就是 ConnectionHolder
  • 恢复当前 Connection 上原先的隔离级别和只读属性,以便重新使用。
  • 释放数据库连接。需要注意的是,对于内层的事务方法来说,并不会真正地释放连接。二是,一般来说 DataSource 使用的是连接池,因此释放的连接并不会真正关闭,而是放回池中,提高使用效率。
  • 重置事务对象的 ConnectionHolder,具体来说 transactionActive 属性设置为 false,说明该数据库连接与事务无关。
java 复制代码
//所属类[cn.stimd.spring.jdbc.datasource.DataSourceTransactionManager]
@Override
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    //解除线程绑定
    TransactionSynchronizationManager.unbindResource(this.dataSource);

    //重置Connection隔离级别和只读属性
    Connection con = txObject.getConnectionHolder().getConnection();
    DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());

    //释放数据库连接
    DataSourceUtils.releaseConnection(con, this.dataSource);

    //重置ConnectionHolder
    txObject.getConnectionHolder().clear();
}

4. 事务模板

4.1 TransactionOperations

由于事务管理器的相关操作是固定的,Spring 提供了模板操作来规范这一过程,简化了事务代码的执行逻辑。TransactionOperations 接口定义了事务模板的行为,execute 方法的作用是执行事务代码,泛型参数 T 代表返回结果。TransactionCallback 接口的作用是以回调的方式执行业务代码,用户需要自行提供实现类。

java 复制代码
public interface TransactionOperations {
    <T> T execute(TransactionCallback<T> action) throws TransactionException;
}

public interface TransactionCallback<T> {
    T doInTransaction(TransactionStatus status);
}

4.2 TransactionTemplate

TransactionTemplate 扩展了 DefaultTransactionDefinition,持有一个事务管理器实例。execute 方法的作用是在事务中执行业务代码,可以分为四步。一是开启事务,二是执行业务代码,三是出现异常时回滚,四是提交事务。回滚事务分三种情况,统一调用 rollbackOnException 方法处理。

java 复制代码
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations{
    private PlatformTransactionManager transactionManager;

    @Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        //1) 开启事务
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            //2) 执行业务代码回调
            result = action.doInTransaction(status);
        }
        //3) 出现异常回滚事务
        //业务异常
        catch (RuntimeException ex) {
            rollbackOnException(status, ex);
            throw ex;
        }
        //错误
        catch (Error err) {
            rollbackOnException(status, err);
            throw err;
        }
        //未知异常
        catch (Throwable ex) {
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        //4) 提交事务
        this.transactionManager.commit(status);
        return result;
    }

    //出现异常回滚
    private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
        try {
            this.transactionManager.rollback(status);
        } catch (RuntimeException ex2) {
            logger.error("Application exception overridden by rollback exception", ex);
            throw ex2;
        } catch (Error err) {
            logger.error("Application exception overridden by rollback error", ex);
            throw err;
        }
    }
}

5. 事务处理流程

5.1 概述

无论是手动调用事务管理器,还是通过事务模板类来完成,都需要 PlatformTransactionManager 接口的三个方法进行配合。单一方法的事务比较简单,出现异常就回滚,正常执行则提交。对于多方法的事务来说,我们不知道哪个方法会出现异常,因此由谁来执行回滚或提交操作是首要考虑的问题 。针对不同的情况,commitrollback 方法会执行不同的分支。我们对多个事务方法之间可能出现的情况进行梳理,列举了以下六种情况,结合流程图来简单分析:

  • 1A-2A:当前方法报错,且是新事务,可以看做是外层方法,直接回滚。

  • 1A-2B:当前方法报错,但不是新事务,说明是内层方法,仅标记为全局回滚,需要外层方法进一步处理。

  • 1B-3A-2A:当前方法正常执行,但检测到全局回滚标记为 true,说明内层方法报错了。且是新事务,说明当前方法是外层方法,仍需要回滚。(提交转回滚流程)

  • 1B-3A-2B:当前方法正常执行,但检测到全局回滚标记为 true,说明内层方法报错了。但不是新事务,说明当前方法也是内层方法,因此仅标记全局回滚。(与第二种情况类似)

  • 1B-3B-4A:当前方法正常执行,全局回滚标记为 false(可能不存在内层方法,也可能内层方法没报错)。且是新事务,说明当前方法是外层方法,执行提交流程。

  • 1B-3B-4B:当前方法正常执行,全局回滚标记为 false。但不是新事务,说明当前方法是内层方法,不做任何处理,最后由外层方法进行提交。

综上所述,在不考虑传播行为的情况下,即假设多个方法属于同一个事务,那么我们可以得出一个总的原则:内层方法不执行实际的提交和回滚操作,内层方法出现异常时仅标记全局回滚,外层方法负责开启事务、提交和回滚的操作

5.2 应用示例

上述六种基本情况是针对单个事务方法而言的,推而广之,我们可以组合出多个事务方法可能出现的所有情况。由于流程图仅代表一个方法的执行流程,对于多个方法来说,会重复执行整个事务流程。假设 A 和 B 两个方法属于同一事务,B 方法是内层方法,先于 A 方法执行完毕。因此执行轨迹先是 B 方法的,然后是 A 方法。为了区别两个方法,这里使用中括号进行分隔。

  • B 方法报错,A 方法正常执行:[1A-2B]-[1B-3A-2A]

  • B 方法和 A 方法均正常执行:[1B-3B-4B]-[1B-3B-4A]

  • B 方法正常执行,A 方法报错:[1B-3B-4B]-[1A-2A]

  • B 方法和 A 方法均报错:[1A-2B]-[1A-2A]

注:示例并没有涵盖所有的六种情况,原因在于第三和第六种情况需要至少三个方法,感兴趣的读者可以自行推断。

6. 测试

6.1 事务管理器

先来看配置类 TransactionConfig 有两个作用,一是导入 DataSourceConfig 配置类,加载数据源;二是注册事务管理器。

java 复制代码
//测试类
@Configuration
@Import(DataSourceConfig.class)
public class TransactionConfig {

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

在测试方法中,先启动 Spring 容器,获取事务管理器组件。然后获取 TransactionStatus 实例,此时事务已经开启。接下来是业务方法,包裹在 try...catch 块中。如果出现异常,调用事务管理器的 rollback 方法进行回滚。如果正常执行,最后调用事务管理器的 commit 方法提交事务。

java 复制代码
//测试方法
@Test
public void testTransactionManager() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfig.class);
    DataSourceTransactionManager txManager = context.getBean(DataSourceTransactionManager.class);

    //开启事务
    TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
    try{
        UserService userService = context.getBean(UserService.class);
        userService.register("Stimd", "12307");
    }catch (Exception ex) {
        //回滚事务,抛出异常
        txManager.rollback(status);
        throw ex;
    }
    //提交事务
    txManager.commit(status);
}

从测试结果可以看到,先开启事务,然后业务方法报错触发回滚,并打印异常堆栈的信息。再观察数据表,并没有新增一条记录,说明事务生效了。

css 复制代码
[Tx] [事务管理] --> 事务不存在,开启新事务
[Tx] [事务管理] --> 回滚事务
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '12307' for key 't_account.idx_unique_phone'

6.2 事务模板

在测试方法中,先是准备工作,然后创建 TransactionTemplate 实例,并调用 exeucte 方法。入参是一个匿名的 TransactionCallback 实现类,在回调方法 doInTransaction 中执行业务代码。测试结果与上一个测试的相同,可以看到事务模板的代码更简洁,所有事务相关的操作都被隐藏了。

java 复制代码
//测试方法
@Test
public void testTransactionTemplate() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfig.class);
    DataSourceTransactionManager txManager = context.getBean(DataSourceTransactionManager.class);

    //创建模板类
    TransactionTemplate template = new TransactionTemplate(txManager);
    template.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
            UserService userService = context.getBean(UserService.class);
            userService.register("Stimd", "12307");
            return null;
        }
    });
}

7. 总结

本节介绍了 Spring 事务的实现流程,即通过事务管理器来控制事务的开启、提交和回滚。这三个方法需要互相配合,才能实现一个完整的事务管理。为此,着重介绍了事务处理流程中可能出现的几种情况。此外,事务管理器的相关操作是固定的,Spring 提供模板类 TransactionTemplate 简化了这一操作。

总的来说,事务管理器和事务模板仍存在不足,主要有两点。一是它们针对的都是单一的事务方法,不能处理多个事务方法的调用,以及更为复杂的事务传播。二是事务操作广泛地出现在应用程序中,使用编程式事务并不是一个明智的选择,我们需要更为便捷的处理方式,即声明式的事务切面。

8. 项目信息

新增修改一览,新增(8),修改(2)。

scss 复制代码
tx
└─ src
   ├─ main
   │  └─ java
   │     └─ cn.stimd.spring
   │        ├─ jdbc
   │        │  └─ datasource
   │        │     ├─ DataSourceTransactionManager.java (+)
   │        │     └─ DataSourceUtils.java (*)
   │        └─ transaction
   │           ├─ support
   │           │  ├─ AbstractPlatformTransactionManager.java (+)
   │           │  ├─ TransactionCallback.java (+)
   │           │  ├─ TransactionOperations.java (+)
   │           │  ├─ TransactionSynchronizationUtils.java (+)
   │           │  └─ TransactionTemplate.java (+)
   │           └─ PlatformTransactionManager.java (+)
   └─ test
      └─ java
         └─ tx
            └─ transaction
               ├─ TransactionConfig.java (+)
               └─ TransactionTest.java (*)

注:+号表示新增、*表示修改

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

相关推荐
懒羊羊不懒@23 分钟前
Java基础语法—最小单位、及注释
java·c语言·开发语言·数据结构·学习·算法
ss27326 分钟前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
DokiDoki之父38 分钟前
MyBatis—增删查改操作
java·spring boot·mybatis
兩尛1 小时前
Spring面试
java·spring·面试
舒一笑1 小时前
🚀 PandaCoder 2.0.0 - ES DSL Monitor & SQL Monitor 震撼发布!
后端·ai编程·intellij idea
Java中文社群1 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme1 小时前
java.nio 包详解
java·python·nio
零千叶1 小时前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝2 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908902 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx