文档章节

mybatis-spring事务处理机制分析

fifadxj
 fifadxj
发布于 2016/11/10 13:48
字数 1756
阅读 1976
收藏 5

Spring官方并没有提供对MyBatis的集成方案,于是MyBatis项目组自己写了一个项目mybatis-spring专门用于在spring中使用MyBatis。

mybatis-spring的实现很大程度上依赖spring jdbc的事务管理,所以我们先看一下在spring中直接使用jdbc访问数据库时是如何处理事务的。无论你是使用@Transactional注解这样的AOP配置方式,还是TransactionTemplate这样的编码方式,最终执行的操作事务的代码都会是类似下面这样

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource);

TransactionStatus status = txManager.getTransaction(def);
try {
    //get jdbc connection...
    //execute sql...

    txManager.commit(status);
}
catch (Exception e) {
    txManager.rollback(status);
    throw e;
}

 可以看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring处理事务的核心api,分别对应事务的开始,提交和回滚。

spring事务处理的一个关键是保证在整个事务的生命周期里所有执行sql的jdbc connection和处理事务的jdbc connection始终是同一个。然后执行sql的业务代码一般都分散在程序的不同地方,如何让它们共享一个jdbc connection呢?这里spring做了一个前提假设:即一个事务的操作一定是在一个thread中执行,且一个thread中如果有多个不同jdbc connection生成的事务的话,他们必须顺序执行,不能同时存在。(这个假设在绝大多数情况下都是成立的)。基于这个假设,spring在transaction创建时,会用ThreadLocal把创建这个事务的jdbc connection绑定到当前thread,接下来在事务的整个生命周期中都会从ThreadLocal中获取同一个jdbc connection。

我们看一下详细调用过程

  • TransactionSynchronizationManager负责从ThreadLocal中存取jdbc connection
  • 创建事务的时候会通过dataSource.getConnection()获取一个新的jdbc connection,然后绑定到ThreadLocal
  • 在业务代码中执行sql时,通过DataSourceUtils.getConnection()从ThreadLocal中获取当前事务的jdbc connection, 然后在该jdbc connection上执行sql
  • commit和rollback事务时,从ThreadLocal中获取当前事务的jdbc connection,然后对该jdbc connection进行commit和rollback

对spring jdbc的事务处理有了了解后,我们来看mybatis是如何通过spring处理事务的。

先看一下配置

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="transactionFactory">
    <bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" />
  </property> 
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
  • mybatis-spring依赖DataSourceTransactionManager来处理事务,并没有创建自己的PlatformTransactionManager实现。
  • mybatis通过SqlSessionFactoryBuilder创建SqlSessionFactory,而mybatis-spring通过SqlSessionFactoryBean创建SqlSessionFactory。
  • 配置使用SpringManagedTransactionFactory来创建MyBatis的Transaction实现SpringManagedTransaction
  • 配置使用SqlSessionTemplate代替通过SqlSessionFactory.openSession()获取SqlSession

然后看其调用过程

可以看到mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。

 

下面结合代码来看

<SqlSessionUtils>:  

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }


  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
        }

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
          }
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
      }
    }
}

执行sql时调用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一个代理类,它通过SqlSessionUtils.getSqlSession()试图从ThreadLocal获取当前事务所使用的SqlSession。如果是第一次获取时会调用SqlSessionFactory.openSession()创建一个SqlSession并绑定到ThreadLocal,同时还会通过TransactionSynchronizationManager注册一个SqlSessionSynchronization。

<SqlSessionSynchronization>:

 public void beforeCommit(boolean readOnly) {
      // Connection commit or rollback will be handled by ConnectionSynchronization or
      // DataSourceTransactionManager.
      // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so
      // they are actually executed.
      // SpringManagedTransaction will no-op the commit over the jdbc connection
      // TODO This updates 2nd level caches but the tx may be rolledback later on! 
      if (TransactionSynchronizationManager.isActualTransactionActive()) {
        try {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
          }
          this.holder.getSqlSession().commit();
        } catch (PersistenceException p) {
          if (this.holder.getPersistenceExceptionTranslator() != null) {
            DataAccessException translated = this.holder
                .getPersistenceExceptionTranslator()
                .translateExceptionIfPossible(p);
            if (translated != null) {
              throw translated;
            }
          }
          throw p;
        }
      }

SqlSessionSynchronization是一个事务生命周期的callback接口,mybatis-spring通过SqlSessionSynchronization在事务提交和回滚前分别调用DefaultSqlSession.commit()和DefaultSqlSession.rollback()

<BaseExecutor>:

public void commit(boolean required) throws SQLException {
    if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
<SpringManagedTransaction>:

this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);  

  public void commit() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
      }
      this.connection.commit();
    }
  }

  public void rollback() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
      }
      this.connection.rollback();
    }
  }
	<DataSourceUtils>:

	/**
	 * Determine whether the given JDBC Connection is transactional, that is,
	 * bound to the current thread by Spring's transaction facilities.
	 * @param con the Connection to check
	 * @param dataSource the DataSource that the Connection was obtained from
	 * (may be {@code null})
	 * @return whether the Connection is transactional
	 */
	public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
		if (dataSource == null) {
			return false;
		}
		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		return (conHolder != null && connectionEquals(conHolder, con));
	}

这里的DefaultSqlSession只会进行一些自身缓存的清理工作,并不会真正提交事务给数据库,原因是这里的DefaultSqlSession使用的Transaction实现为SpringManagedTransaction,SpringManagedTransaction在提交事务前会检查当前事务是否应该由spring控制,如果是,则不会自己提交事务,而将提交事务的任务交给spring,所以DefaultSqlSession并不会自己处理事务。

<SpringManagedTransaction>: 

 public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  /**
   * Gets a connection from Spring transaction manager and discovers if this
   * {@code Transaction} should manage connection or let it to Spring.
   * <p>
   * It also reads autocommit setting because when using Spring Transaction MyBatis
   * thinks that autocommit is always false and will always call commit/rollback
   * so we need to no-op that calls.
   */
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

DefaultSqlSession执行sql时,会通过SpringManagedTransaction调用DataSourceUtils.getConnection()从ThreadLocal中获取jdbc connection并在其上执行sql。

总结:mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。

mybatis-spring做的最主要的事情是:

  1. 在SqlSession执行sql时通过用SpringManagedTransaction代替mybatis的JdbcTransaction,让SqlSession从spring的ThreadLocal中获取jdbc connection。
  2. 通过注册事务生命周期callback接口SqlSessionSynchronization,让SqlSession有机会在spring管理的事务提交或回滚时清理自己的内部缓存。

© 著作权归作者所有

上一篇: Sharding-JDBC学习
下一篇: 短信验证码
fifadxj
粉丝 5
博文 28
码字总数 18063
作品 0
浦东
高级程序员
私信 提问
SSM:MyBatis与Spring的整合

MyBatis与Spring的整合 一:Spring整合MyBatis的准备工作: (1.)在项目中加入Spring,ByBatis及整合相关的jar文件(2.)建立开发目录结构,创建实体类(3.)创建数据访问接口(4.)配置SQL...

廉价香水
2017/10/30
0
0
推荐几个自己写的Java后端相关的范例项目(转载)

http://wosyingjun.iteye.com/blog/2312553 这里推荐几个自己写的范例项目,主要采用SSM(Spring+SpringMVC+Mybatis)框架,分布式架构采用的是(dubbo+zookeeper)。范例项目的好处是简单易...

指尖的舞者
2016/09/27
147
0
MyBatis3整合Spring3的Transaction事务处理

正如第二版,Spring 仅支持 iBatis2。那么我们就想将 MyBatis3 的支持加入到 Spring3.0(参考 Spring 的 Jira 的问题)中。 不幸的是,Spring 3.0 的开发在 MyBatis 3.0 官方发布前就结束了。...

鉴客
2012/02/23
2.8K
0
Mybatis学习(3)—— 事务机制

事务的实现 对数据库的事务而言,应该具有以下几点:创建(create)、提交(commit)、回滚(rollback)、关闭(close)。对应地,MyBatis将事务抽象成了Transaction接口: org.apache.ibait...

叶枫啦啦
2018/07/12
0
0
MiniDao_1.6-SNAPSHOT 版本发布

MiniDao-PE 简介 MiniDao-PE 是一种持久化解决方案,类似mybatis的持久层解决方案,可以轻松集成Hibernate工程,事务统一管理,解决了Hibernate工程想支持mybaits的功能问题。Hibernate的最佳...

多吉Q
2016/07/20
4K
2

没有更多内容

加载失败,请刷新页面

加载更多

Android Camera模块解析之拍照

最近学习Android的camera模块,本文先介绍一下camera2的api,然后给出android camera拍照的例子,讲解一下camera 拍照的原因知识,与大家共勉。 camera2 介绍 android camera拍照功能介绍 一...

天王盖地虎626
16分钟前
5
0
Excel 曝Power Query安全漏洞

近日,Mimecast 威胁中心的安全研究人员,发现了微软 Excel 电子表格应用程序的一个新漏洞,获致 1.2 亿用户易受网络攻击。其指出,该安全漏洞意味着攻击者可以利用 Excel 的 Power Query 查...

linuxCool
23分钟前
1
0
SpringBoot | 第三十八章:基于RabbitMQ实现消息延迟队列方案

前言 >前段时间在编写通用的消息通知服务时,由于需要实现类似通知失败时,需要延后几分钟再次进行发送,进行多次尝试后,进入定时发送机制。此机制,在原先对接银联支付时,银联的异步通知也...

oKong
23分钟前
6
0
Excel 曝Power Query安全漏洞

近日,Mimecast 威胁中心的安全研究人员,发现了微软 Excel 电子表格应用程序的一个新漏洞,获致 1.2 亿用户易受网络攻击。其指出,该安全漏洞意味着攻击者可以利用 Excel 的 Power Query 查...

linux-tao
39分钟前
1
0
Java - NIO框架

Java NIO提供了与标准IO不同的IO工作方式。 Channels and Buffers(通道和缓冲区) 标准IO基于字节流和字符流,NIO基于通道(Channel)和缓冲区(Buffer)。数据从channel读取到buffer,或从Buffe...

丰田破产标志
42分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部