【Spring源码】- 11 Spring AOP之编程式事务

对于普通项目不太关注使用Spring扩展点进行定制时,个人觉得使用Spring两个最大价值:IoC容器管理Bean,另一个就是事务管理。Spring使用声明式事务方式,对业务代码没有侵入就可以实现事务,如果自己去管理事务的话,将会带来非常大的额外工作量,繁琐且会对业务代码侵入,影响代码质量。所以,如果你去问一些开发者为什么使用Spring时,可能他会给你一个他最直观感受就是简化事务管理。Spring事务管理就是一个借助AOP实现的一个典型的且具有很大实用价值的经典案例,今天,我们就来分析下Spring中的事务管理。

核心API

Spring中对事务这块抽象出三个核心接口:TransactionDefinitionPlatformTransactionManagerTransactionStatus。下面就来逐一分析下这三个接口的作用。

TransactionDefinition

TransactionDefinition接口定义如下,该接口很好理解,就是存放的事务的配置信息,如:事务隔离级别、事务传播特性、超时、只读事务等等。

public interface TransactionDefinition {

 //事务的7个传播行为
 int PROPAGATION_REQUIRED = 0;
 int PROPAGATION_SUPPORTS = 1;
 int PROPAGATION_MANDATORY = 2;
 int PROPAGATION_REQUIRES_NEW = 3;
 int PROPAGATION_NOT_SUPPORTED = 4;
 int PROPAGATION_NEVER = 5;
 int PROPAGATION_NESTED = 6;
    
    //事务的5个隔离级别
 int ISOLATION_DEFAULT = -1;//默认的隔离级别,表示使用底层数据库的默认隔离级别
 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;

 //事务超时时间 事务在超时前能运行多久,超过时间后,事务被回滚
 int TIMEOUT_DEFAULT = -1;
    //返回传播行为
 int getPropagationBehavior();
    //返回隔离级别
 int getIsolationLevel();
    //返回超时时间
 int getTimeout();
    //是否只读事务 只读事务不修改任何数据,事务管理器可以针对只读事务应用一些优化措施,提高运行性能
 boolean isReadOnly();
    //返回事务名称
 String getName();
}

PlatformTransactionManager

PlatformTransactionManager事务管理器,其定义见下,只提供了与事务相关的三个操作:事务创建、事务提交、事务回滚。

public interface PlatformTransactionManager {
 /**
  * 返回当前活动的事务或创建一个新的事务,参数definition描述了事务的属性,比如传播行为、隔离级别、超时等
  */
 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

 /**
  * 根据给定事务的状态提交给定事务
  */
 void commit(TransactionStatus status) throws TransactionException;

 /**
  * 执行给定事务的回滚
  */
 void rollback(TransactionStatus status) throws TransactionException;
}

Spring抽象出PlatformTransactionManager事务管理器,主要是因为事务操作相关需要依赖具体的ORM框架实现,Spring可以和很多ORM框架进行集成,所以就存在很多其实现类。这里同样采用了Spring常规套路:使用模板设计模式,定义一个抽象类AbstractPlatformTransactionManager实现了事务处理的常规流程,涉及到具体底层实现定义出抽象方法供具体实现类扩展。常见的实现类见下:

大致描述如下:

  • JmsTransactionManager:实现ActiveMQ中间件事务管理;

  • HibernateTransactionManager:和Hibernate集成使用的事务管理器;

  • DataSourceTransactionManager:常规的JDBC方式进行事务管理器,比如和Mybatis集成就是使用这个事务管理器;

  • JpaTransactionManagerJpa事务管理器;

  • JtaTransactionManagerJta事务管理器;

TransactionStatus

PlatformTransactionManager主要用于管理所有的事务,而TransactionStatus则代表的是具体某一个事务运行状态,通过它就可以对具体某个事务进行提交、回滚操作。

public interface TransactionStatus extends SavepointManager, Flushable {

 //当前事务状态是否是新事务
 boolean isNewTransaction();

 //当前事务是否有保存点
 boolean hasSavepoint();

 //设置当前事务应该回滚,如果设置这个,则commit不起作用
 void setRollbackOnly();

 //当前事务是否应该回滚
 boolean isRollbackOnly();

 //用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响
 @Override
 void flush();

 //当前事务是否已经完成
 boolean isCompleted();

}

该接口继承于SavepointManager接口,SavepointManager接口基于JDBC 3.0保存点的分段事务控制能力提供了嵌套事务的机制。

简单梳理下它们三者关系:平台事务管理器真正管理事务对象,其会根据事务定义信息TransactionDefinition进行事务管理,在管理事务中产生一些状态信息会记录到TransactionStatus中。

编程式事务

平时开发中我们更多使用@Transactional注解方式即可很简单的搞定事务,简化同时也隐藏了Spring事务实现方式,即声明式事务。今天我们主要是从编程式事务入手,研究下Spring底层具体实现事务的细节。

Spring提供了一个轻量级的ORM工具类:JdbcTemplate,如下:

String sql = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 1111)";
jdbcTemplate.update(sql);

默认是不能进行手工事务管理的,即jdbcTemplate执行完成后会自动提交,如下,代码2处抛出异常,但是代码1已经自动提交了,不受影响,代码3则无法执行。

jdbcTemplate.update(sql1);     //1
int i=1/0;                     //2
jdbcTemplate.update(sql2);     //3

如果我们想让代码1和代码3放到同一个事务中怎么做:

1、首先,创建一个事务管理器,因为JdbcTemplate直接基于JDBC,使用DataSourceTransactionManager类型即可:

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

2、测试用例

@Test
public void testTransaction() {
 DefaultTransactionDefinition def = new DefaultTransactionDefinition();
 //1.开启一个事务
 TransactionStatus status = transactionManager.getTransaction(def);
 try {
  String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
  String sql2 = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 9999)";
  //2.执行sql1
  jdbcTemplate.update(sql1);
  int i = 1 / 0;
  //3.执行sql2
  jdbcTemplate.update(sql2);
  //4.正常时进行事务提交
  transactionManager.commit(status);
 }catch (Exception e){
  //5.出现异常后进行事务回滚
  transactionManager.rollback(status);
  e.printStackTrace();
 }
}

int i = 1 / 0这句注释掉,执行后数据库表中正常插入两条记录,然后把int i = 1 / 0,执行后数据库表中一条记录都没有新增,说明事务起作用了。

Spring提供了一个专门处理事务工具类:TransactionTemplate

1、创建TransactionTemplate实例:

@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager){
 return new TransactionTemplate(transactionManager);
}

2、编写测试用例:

@Test
public void testTransactionTemplate() {
 transactionTemplate.executeWithoutResult( status -> {
  try {
   String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
   String sql2 = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 9999)";
   //2.执行sql1
   jdbcTemplate.update(sql1);
   int i = 1 / 0;
   //3.执行sql2
   jdbcTemplate.update(sql2);
  }catch (Exception e){
   //5.出现异常后进行事务回滚,注意,如果这里不进行回滚依然提交,sql1会被提交,sql2由于异常导致没有执行到
   status.setRollbackOnly();
  }
 });
}

transactionTemplate通过接口回调方式,在方法中可以获取到事务运行状态信息TransactionStatus,然后通过它即可实现提交or回滚。如果需要回滚事务,只需要执行status.setRollbackOnly()即可,否则就会进行事务提交。

原理分析

一般使用如下方式就可以让JdbcTemplate在事务中执行,那这里原理到底是什么呢?下面我们就来逐一分下下。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
 String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
 jdbcTemplate.update(sql1);
    //事务提交
 transactionManager.commit(status);
}catch (Exception e){
    //事务回滚
 transactionManager.rollback(status);
}

开启事务

首先,来看下PlatformTransactionManager#getTransaction这句代码背后做了哪些事情。PlatformTransactionManager#getTransactionAbstractPlatformTransactionManager抽象类中提供通用实现,即采用模板设计模式。

1、doGetTransaction():创建事务对象

Object transaction = doGetTransaction();

大致描述:

  • doGetTransaction()创建事务对象,涉及到具体的orm框架,比如DataSourceTransactionManager这里创建的是DataSourceTransactionObject

  • 然后从TransactionSynchronizationManager#resources中获取到当前线程的ConnectionHolder,由于可能存在多数据源,所以resources类型是ThreadLocal<Map<Object, Object>>,即每个线程对应的是一个Map,key就是DataSourcevalueConnectionHolder

  • 由于当前线程中还没有绑定资源,所以这里获取到的ConnectionHolder实际上是null

2、startTransaction():开启事务

startTransaction(def, transaction, debugEnabled, suspendedResources);

主要工作是在startTransaction()方法中完成,现在我们来分析下这个方法。

1、newTransactionStatus():创建出TransactionStatus实例,相当于将之前创建的事务对象DataSourceTransactionObject进行了一层包装,将各种类型包装成一个统一的TransactionStatus类型供调用方使用,如果是DataSourceTransactionManager管理器,这里实际实现类是DefaultTransactionStatus

DefaultTransactionStatus status = newTransactionStatus(
    definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

2、doBegin():这个方法涉及底层实现,需要依赖具体的orm实现框架,所以在AbstractPlatformTransactionManager没有实现,需要具体的事务管理器实现类实现。这里使用的是DataSourceTransactionManager,其定义如下:

 protected void doBegin(Object transaction, TransactionDefinition definition) {
  DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  Connection con = null;

  try {
   if (!txObject.hasConnectionHolder() ||
     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
    // 从连接池DataSource中获取一个连接Connection
    Connection newCon = obtainDataSource().getConnection();
    txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
   }

   txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
   con = txObject.getConnectionHolder().getConnection();

   // readOnly、transactionIsolation设置
   Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
   txObject.setPreviousIsolationLevel(previousIsolationLevel);
   txObject.setReadOnly(definition.isReadOnly());

   if (con.getAutoCommit()) {
    txObject.setMustRestoreAutoCommit(true);
    if (logger.isDebugEnabled()) {
     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
    }
    // 将Connection的autoCommit设置成false
    con.setAutoCommit(false);
   }

   prepareTransactionalConnection(con, definition);
   txObject.getConnectionHolder().setTransactionActive(true);

   int timeout = determineTimeout(definition);
   if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
   }

   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);
  }
 }

大致描述:

  • Connection newCon = obtainDataSource().getConnection():从连接池DataSource获取到一个底层Connection,然后将其包装成ConnectionHolder类型,并设置到DataSourceTransactionObject中;

  • con.setAutoCommit(false)ConnectionautoCommit设置成false,这样才能手动管理事务;

  • TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()):将配置好的Connection包装类ConnectionHolder绑定到上下文线程中:TransactionSynchronizationManager#resources,这样在其它地方需要使用到数据库连接时就可以从这里获取到配置好的Connection

分析到这里,PlatformTransactionManager#getTransaction大致工作内容基本已经清楚:

  1. 利用DataSource获取到底层连接Connection

  2. 然后进行一些配置,主要是的是将ConnectionautoCommit设置成false,这样才能手动管理事务;

  3. 通过bindResource()将其绑定到线程上下文中:TransactionSynchronizationManager#resources,这样在其它地方需要使用到数据库连接时就可以从这里获取到配置好的Connection

事务操作

transactionManager.getTransaction(def)方法开启事务后,然后就jdbcTemplate.update(sql1)就开始执行数据库操作,追踪代码发现这里关键位于

JdbcTemplate#execute()中:

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
 Assert.notNull(action, "Callback object must not be null");

 Connection con = DataSourceUtils.getConnection(obtainDataSource());
 Statement stmt = null;
 try {
  stmt = con.createStatement();
  applyStatementSettings(stmt);
  T result = action.doInStatement(stmt);
  handleWarnings(stmt);
  return result;
 }
 catch (SQLException ex) {
  String sql = getSql(action);
  JdbcUtils.closeStatement(stmt);
  stmt = null;
  DataSourceUtils.releaseConnection(con, getDataSource());
  con = null;
  throw translateException("StatementCallback", sql, ex);
 }
 finally {
  JdbcUtils.closeStatement(stmt);
  DataSourceUtils.releaseConnection(con, getDataSource());
 }
}

关键是Connection con = DataSourceUtils.getConnection(obtainDataSource());这里获取Connection,然后进行各种JDBC操作。

跟踪DataSourceUtils.getConnection()逻辑大致是:TransactionSynchronizationManager.getResource(dataSource);从线程上下文中获取;获取不到再回去DataSource中获取。上个流程PlatformTransactionManager#getTransaction中,已经将配置好的ConnectionHolder绑定到了线程上下文中,所以这里就可以获取到配置好的ConnectionHolder,进而获取到Connection

事务提交/回滚

操作完成后,就可以使用transactionManager.commit(status)transactionManager.rollback(status)进行事务提交或回滚。

比如事务提交,核心是在事务管理器实现类的doCommit()方法中:

protected void doCommit(DefaultTransactionStatus status) {
 DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
 Connection con = txObject.getConnectionHolder().getConnection();
 try {
  con.commit();
 }
 catch (SQLException ex) {
  throw new TransactionSystemException("Could not commit JDBC transaction", ex);
 }
}

原理很简单,就是从TransactionStatus获取到底层的Connection,然后执行commit()方法即可。事务回滚逻辑基本一致,就不再分析。

总结

TransactionDefinitionPlatformTransactionManagerTransactionStatus三大核心类如何相互配合实现Spring中编程式事务的原理应该大致比较了解了。这里再简单总结下:

  1. 配置:开启事务时从DataSource获取到底层Connection实例,然后进行配置,最主要的是将其autoCommit设置成false

  2. 绑定:然后通过TransactionSynchronizationManager.bindResource()绑定到线程上下文中;

  3. 使用:需要使用事务操作时,就从上步绑定的线程上下文中获取配置好的Connection进行数据库操作即可;

  4. 提交/回滚:当对TransactionStatus执行commitrollback时,由TransactionStatus是可以直接获取到Connection实例,然后对其进行commitrollback即可。

             长按识别关注,持续输出原创

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云原生生态实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值