【重写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编程探微】,加群一起讨论。

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

相关推荐
江节胜-胜行全栈AI19 分钟前
Java-腾讯云短信模板兼容阿里云短信模板-短信模板参数生成
java·阿里云·腾讯云
z263730561123 分钟前
springboot继承使用mybatis-plus举例相关配置,包括分页插件以及封装分页类
spring boot·后端·mybatis
追逐时光者3 小时前
分享一个纯净无广、原版操作系统、开发人员工具、服务器等资源免费下载的网站
后端·github
JavaPub-rodert4 小时前
golang 的 goroutine 和 channel
开发语言·后端·golang
TFHoney5 小时前
Java面试第十一山!《SpringCloud框架》
java·spring cloud·面试
ivygeek6 小时前
MCP:基于 Spring AI Mcp 实现 webmvc/webflux sse Mcp Server
spring boot·后端·mcp
日暮南城故里6 小时前
Java学习------初识JVM体系结构
java·jvm·学习
GoGeekBaird6 小时前
69天探索操作系统-第54天:嵌入式操作系统内核设计 - 最小内核实现
后端·操作系统
鱼樱前端7 小时前
Java Jdbc相关知识点汇总
java·后端
小嘚7 小时前
springCloud的学习
学习·spring·spring cloud