转载

Spring 源码(十一 ): Spring AOP 之编程式事务

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

核心API

Spring 源码(十一 ): Spring AOP 之编程式事务

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 实现了事务处理的常规流程,涉及到具体底层实现定义出抽象方法供具体实现类扩展。常见的实现类见下:

Spring 源码(十一 ): Spring AOP 之编程式事务

大致描述如下:

  • 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
    DataSource
    value
    ConnectionHolder
    
  • 由于当前线程中还没有绑定资源,所以这里获取到的 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)
    Connection
    autoCommit
    false
    
  • TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder())
    Connection
    ConnectionHolder
    TransactionSynchronizationManager#resources
    Connection
    

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

  1. 利用 DataSource 获取到底层连接 Connection
  2. Connection
    autoCommit
    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
    commit
    rollback
    TransactionStatus
    Connection
    commit
    rollback
    

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

Spring 源码(十一 ): Spring AOP 之编程式事务

原文  https://mp.weixin.qq.com/s/-DrIXySsUo486kghiwQbtA
正文到此结束
Loading...