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工程,就可以调试源码啦...

时间: 2024-08-24 08:23:32

spring与ibatis集成之事务部分源码解析的相关文章

深入浅出 Spring Cache 使用与整合(附源码解析)

深入浅出 Spring Cache 使用与整合(附源码解析) 个人开发环境 java环境:Jdk1.8.0_60 编译器:IntelliJ IDEA 2019.1 springCache官方文档:https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/integration.html#cache 一.Spring缓存抽象 SpringCache产生的背景其实与Spring产生的背景有点类似.由于Jav

Spring核心框架 - AOP的原理及源码解析

一.AOP的体系结构 如下图所示:(引自AOP联盟) 层次3语言和开发环境:基础是指待增加对象或者目标对象:切面通常包括对于基础的增加应用:配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现. 层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象.切面和配置使用AOP的API转换.抽象.封装成面向方面中的逻辑模型. 层次1底层编织实现模块:主要是将面向方面系统抽象封

spring源码解析前瞻

IOC.AOP是spring的2个核心特性.理解这2个特性,有助于更好的解析源码. IOC:控制反转.把创建对象的权利交给框架,这有利于解耦. public class PageController { public String showPage(){ PageService page = new PageService(); return ""; } } 原先PageController中使用PageService,需要自己new创建对象,使用spring后,由容器创建PageSe

Spring源码解析-applicationContext

Demo uml类图 ApplicationContext ApplicationListener 源码解析 主流程 obtainFreshBeanFactory prepareBeanFactory invokeBeanFactoryPostProcessors registerBeanPostProcessors registerListeners finishRefresh 总结 在已经有BeanFactory可以完成Ioc功能情况下,spring又提供了ApplicationContex

Spring事务源码解析(二)获取增强

在上一篇文章@EnableTransactionManagement注解解析中,我们搭建了源码阅读的环境,以及解析了开启Spring事务功能的注解@EnableTransactionManagement的实现逻辑 在进行接下来的源码解析之前我想大家应该知道,当我们使用传统的jdbc应用事务的时候是不是做了如下操作: 开启事务 save.update.delete等操作 出现异常进行回滚 正常情况提交事务 而在Spring中我们好像只需要关心第三步,也就是我们的业务,而其他的操作都不需要关心.那么

spring mvc源码解析

1.从DispatcherServlet开始     与很多使用广泛的MVC框架一样,SpringMVC使用的是FrontController模式,所有的设计都围绕DispatcherServlet 为中心来展开的.见下图,所有请求从DispatcherServlet进入,DispatcherServlet根据配置好的映射策略确定处理的 Controller,Controller处理完成返回ModelAndView,DispatcherServlet根据配置好的视图策略确定处理的 View,由V

SPRING源码解析-SPRING 核心-IOC

IoC 和 AOP是Spring的核心, 是Spring系统中其他组件模块和应用开发的基础.透过这两个模块的设计和实现可以了解Spring倡导的对企业应用开发所应秉承的思路: 易用性. POJO开发企业应用, 直接依赖于Java语言,而不是容器和框架. 提升程序的可测试性,提高软件质量. 提供一致性编程模型,面向接口的编程 降低应用的负载和框架的侵入性.IoC和AOP实现. 不作为现有解决方案的替代,而是集成现有. IoC和AOP这两个核心组件,特别是IoC容器,使用户在使用Spring完成PO

消息中间件 RocketMQ源码解析:事务消息

关注微信公众号:[芋艿的后端小屋]有福利: RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问每条留言都将得到认真回复.甚至不知道如何读源码也可以请教噢. 新的源码解析文章实时收到通知.每周更新一篇左右. 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请求 2.3 Broker 生成

Spring IoC源码解析——Bean的创建和初始化

Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以和Struts框架,MyBatis框架等组合使用. IoC介绍 IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控