spring与ibatis集成之事务部分源码解析

Posted ze2200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring与ibatis集成之事务部分源码解析相关的知识,希望对你有一定的参考价值。

ibatis是一个非常优秀的半自动ORM框架,相较于许多人认为编写sql和配置字段映射会降低开发效率,我认为在数据库最易成为系统瓶颈的情况下,开发人员必须通过手动编写sql来保证sql执行的高效,并在编写过程中思考表结构的设计是否合理。网上已有许多关于ibatis架构和映射实现原理解析的文章,本文主要讨论ibatis和spring集成后事务管理的实现。
在spring和ibatis集成后,有两种方式执行sql:第一种是dao继承spring的SqlMapClientDaoSupport,具体的sql通过调用父类的getSqlMapClientTemplate()得到的SqlMapClientTemplate实例执行;第二种同样是dao继承SqlMapClientDaoSupport,但具体的sql通过调用getSqlMapClient()得到的SqlMapClient实例执行,或不继承SqlMapClientDaoSupport,而是直接在dao注入SqlMapClient实例来执行sql。两种方式都可以正常执行sql,为什么我们要选择第一种呢?关键原因是第一种方式会将ibatis的事务委托给外部的spring管理,而第二种方式ibatis将自己管理事务,从SqlMapClient实例返回前将提交事务,最终导致spring无法完整地回滚事务。

源码分析基于 spring 3.0 + ibatis 2.3.0

ibatis事务管理相关对象类图

ibatis事务管理接口SqlMapTransactionManager的startTransaction&commitTransaction&endTransaction操作的实现,最终是调用TransactionManager的begin&commit&end操作实现。ibatis的事务仅针对单次执行的sql,也就是说调用两次SqlMapClient.update,本身是执行了两次事务。这样的事务管理在实际的应用中意义不大,但这是我们分析ibatis和spring集成后事务管理的基础,因此有必要先梳理清楚ibatis自带的事务管理。

ibatis事务操作时序图

如果要了解SqlMapClient及相关的SqlMapExecutorDelegate、TransactionManager实例是如何创建的,请阅读SqlMapClientFactoryBean的afterPropertiesSet()方法。从时序图可以了解到,SqlMapClient.update操作最终调用SqlMapExecutorDelegate.update操作(CRUD的实现都是这样),SqlMapExecutorDelegate.update方式实现了事务管理,代码解析如下图:

SqlMapExecutorDelegate.update(SessionScope session, String id, Object param) throws SQLException {
  int rows = 0;
  MappedStatement ms = getMappedStatement(id);
  //从SessionScope实例获取Transaction实例。

      //采用SqlMapClient.update执行CRUD操作,trans为空;

      //采用spring的SqlMapClientTemplate.update执行CRUD操作,trans为UserProvidedTransaction实例,具体实现为

  Transaction trans = getTransaction(session);
  boolean autoStart = trans == null;
  try {
    //尝试开始事务

    trans = autoStartTransaction(session, autoStart, trans);

           ...执行sql...(CUD操作会置SessionScope.commitRequired=true,以便后续提交事务)

           //尝试提交事务:1、如果是批量操作且未执行,执行;2、如果SessionScope.commitRequired || forceCommit,提交事务

           //3、置SessionScope.transactionState为TransactionState.STATE_COMMITTED

    autoCommitTransaction(session, autoStart);
  } finally {

           //尝试结束事务:1、如果是批量操作,清理批量操作相关数据结构(Statment,语句List,结果List);

           //2、如果SessionScope.transactionState!=TransactionState.STATE_COMMITTED(表示发生了异常),回滚事务;

           //3、close Statement;TransactionManager执行中事务计数器减一;SessionScope.transaction = null,transactionState=TransactionState.STATE_ENDED

    autoEndTransaction(session, autoStart);
  }
  return rows;
}

protected Transaction autoStartTransaction(SessionScope session, boolean autoStart, Transaction trans) throws SQLException {
  Transaction transaction = trans;
  if (autoStart) {
    //最终调用TransactionManager.begin生成Transaction实例
    //默认SessionScope.commitRequired=false,此属性会决定在调用事务提交操作时是否调用Connection.commit()操作
    session.getSqlMapTxMgr().startTransaction();
    transaction = getTransaction(session);
  }
  return transaction;
}

从上面的解释中,我们可以看到,ibatis是否启用事务自动管理的关键是SessionScope是否持有了Transaction实例。也就是说如果我们在代码执行到此处之前为SessionScope设置了Transaction实例,那么ibatis事务就可以委托给外部的spring管理(事务管理的关键,使用同一个Connection的实现逻辑将在后面提及,此处仅是分析将ibatis事务委托给spring管理的实现关键点)。

下面分析ibatis事务委托给spring管理的代码实现。分析前,有必要大致清楚spring的事务管理是如何实现的(仅仅是帮助理解,不会细致分析spring的事务实现)。spring有两种事务配置方式,声明式和编程式,两种方式对应的事务管理代码实现是在TranactionInterceptor.invoke()(spring4是调用父类的invokeWithinTransaction方法)方法。

spring事务管理的时序图:

代码解析如下,基于事务传播属性为:PROPAGATION_REQUIRED:

TranactionInterceptor.invoke(final MethodInvocation invocation) {
  ...

  //获取用户定义的事务传播属性,和事务隔离级别
  final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
  ...
  if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    //1、生成Transaction实例;2、DataSource获取Connection,设置到Transaction,并将Connection绑定到线程变量,本次事务内
    // 调用DataSourceUtils.getConnection(DataSource) 获取到的是同一个Connnection实例;
    //3、生成TransactionInfo实例(持有事务管理所需的全部实例),绑定到线程变量
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    try {
      //执行具体的应用代码,最终执行到dao
      retVal = invocation.proceed();
    } catch (Throwable ex) {
      //回滚事务。有兴趣可自行阅读
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;

    } finally {

      //恢复线程变量持有的原TransactionInfo实例
      cleanupTransactionInfo(txInfo);
    }
    //提交事务,清除线程变量持有的Connection,恢复Connection的autoCommit、
    //transactionIsolation、readOnly等属性,将Connection返回连接池
    commitTransactionAfterReturning(txInfo);
    return retVal;
  } else {
    // CallbackPreferringPlatformTransactionManager的事务执行逻辑,将事务管理委托给了j2ee容
    //器提供商提供的TransactionManager,websphere的如WebSphereUowTransactionManager.
    ...
  }
}

spring开启事务管理的代码解析:
//如果需要,创建事务。什么情况下不需要,请结合spring的7个事务传播属性确定
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
  ...
  TransactionStatus status = null;
  if (txAttr != null) {
    if (tm != null) {
      //调用配置的TransactionManager生成Transaction对象并开启事务
      status = tm.getTransaction(txAttr);
    }
  }
  //设置TransactionInfo实例,并绑定到线程变量
  return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

//调用PlatformTransactionManager实例(本文配置为DataSourceTransactionManager)的getTransaction()操作获取事务,并开启事务,如果有必要。

//模板模式实现,AbstractPlatformTransactionManager.getTransaction()实现了主流程
AbstractPlatformTransactionManager.getTransaction(TransactionDefinition definition) {
  //调用配置的事务管理器DataSourceTransactionManager.doGetTransaction()生成事务实例
  Object transaction = doGetTransaction();
  ...
  if (isExistingTransaction(transaction)) {
    //嵌套事务下,spring事务传播属性语义的实现逻辑
    return handleExistingTransaction(definition, transaction, debugEnabled);
  }
  ...
  if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
    throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation \'mandatory\'");
  } else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    //系统将执行本段代码,本文不涉及嵌套事务和事务传播行为各种组合的分析
    ...
    try {
      DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
      //调用具体的事务管理器DataSourceTransactionManager开启事务
      doBegin(transaction, definition);

                 //设置TransactionSynchronizationManager,spring事务管理核心属性管理器
      prepareSynchronization(status, definition);
      return status;
    } catch (RuntimeException ex) {}
  } else {
    //事务传播属性中,不需要事务行为的逻辑分支
    return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
  }
}
//开始事务
DataSourceTransactionManager.doBegin(Object transaction, TransactionDefinition definition) {
  ...
  try {
    //无法从事务实例中获取Connection
    if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
      //从DataSource获取Connection实例
      Connection newCon = this.dataSource.getConnection();
      ...
    }
    ...设置Connnection的transactionIsolation等属性
    if (txObject.isNewConnectionHolder()) {
      //绑定Connection到线程变量,本事务内使用DataSourceUtils.getConnection获取的是同一个Connection
      TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
    }
  } catch (SQLException ex) {...}
}

spring开启事务管理,绑定了Connection到线程变量,所有将事务委托给spring管理的代码,调用DataSourceUtils.getConnection(),获取到的是同一个Connection实例,这是事务实现的关键。下面是ibatis将事务委托给spring管理的代码实现分析:

SqlMapClientDaoSupport.getSqlMapTemplate().update(final String statementName, final Object parameterObject) {

  return execute(new SqlMapClientCallback<Integer>() {
    public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
      return executor.update(statementName, parameterObject);
    }
  });
}
SqlMapClientTemplate.execute(SqlMapClientCallback<T> action) {
  //新建SqlMapSessionImpl实例
  SqlMapSession session = this.sqlMapClient.openSession();
  try {
    ...
    //dataSource是否为TransactionAwareDataSourceProxy实例的逻辑,请参考
    //SqlMapClientFactoryBean.afterPropertiesSet,由useTransactionAwareDataSource控制
    //此处值为false;如果transactionAware==true,将导致spring无法管理本次sql执行事务
    boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);

    try {
      //ibatisCon为空
      ibatisCon = session.getCurrentConnection();
      if (ibatisCon == null) {
        //调用DataSourceUtils.doGetConnection(dataSource)获取spring事务开始时绑定到线程变量的Connection实例
        springCon = transactionAware ? dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource);
        //最终调用SqlMapExectorDelegate.setUserProvidedTransaction()
        //1,生成UserProvidedTransaction实例;2,将springCon设置到事务实例;3,将事务实例设置到session变量持有的SessionScope实例
        //前面的分析中已经提到,ibatis的事务管理会检查SessionScope是否持有的Transaction,如果持有,
        //就不开启自带的事务管理,故setUserConnection操作实现了将ibatis事务委托给外部管理的功能。
        session.setUserConnection(springCon);
      }
      ...
    } catch() {}
    try {
      //执行ibatis CRUD操作
      return action.doInSqlMapClient(session);
    } catch (SQLException ex) {

           } finally {

                 ...

                 //将ConnectionHolder中,对Connection使用的计数器减1
       DataSourceUtils.doReleaseConnection(springCon, dataSource);
    }
  } finally {}

}

从以上啰嗦的分析中可以看出,spring和ibatis的事务整合实现逻辑并不复杂,只要理解了ibatis是如何将事务委托给外部管理的设计,和事务的sql操作必须同一个Connection的原则,理解SqlMapClientTemplate相关的事务整合代码就没有任何问题。框架极大降低了开发难度的同时,也屏蔽了许多我们应该掌握的技术细节,学习框架的实现方案,了解技术细节,对实际的应用开发是非常有益的。

最后提供一个调试ibatis源码的笨方法:在你的工程里新建一个ibatis工程,导入源码,添加依赖的jar,将你的dao工程依赖的ibatis改为新建的ibatis工程,就可以调试源码啦...

 

以上是关于spring与ibatis集成之事务部分源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring 与 MyBatis 事务管理源码解析

2RocketMQ 源码解析之 与 Spring Boot 集成

2RocketMQ 源码解析之 与 Spring Boot 集成

1Nacos 配置中心源码解析之 集成 Spring Cloud

1Nacos 配置中心源码解析之 集成 Spring Cloud

Spring源码深度解析,事务案例讲解高级