spring和mybatis的事务相关源码分析

目录

前言

一、connection的获取过程

二、sqlsession的相关源码分析

1.整体流程

2.sqlsession的获取

3.sqlsession的提交方法

三、SqlSessionSynchronization

总结





前言

在有事务的情况下,为了保证事务的特性,spring和mybatis结合的过程做了哪些处理呢?本文从源码上分析,首先分析如何保证spring和mybatis获取connection的一致性,然后分析sqlsession获取的过程以及带来的影响。


一、connection的获取过程

在有spring事务参与的情况下,mybatis获取connection调用的是spring自己的dataSourceUtils的方法,在前面spring构建事务的过程中也是使用的同样方法,获取connection,并将connection写入本地连接中,从而保证mybatis查询过程中使用的connection与spring事务中的一致,保证spring事务中的提交和回滚能够生效。

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

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







二、sqlsession的相关源码分析

参与的情况在mapperFactoryBean中的getObject方法中获取的sqlsession是SqlSessionTemplate,本文主要针对有事务参与的情况下,sqlsessiontemplate请求过程中的一些问题进行分析,比如sqpsession的线程安全性,sqlsession的获取流程,事务提交过程等,过程可能比较乱,基本就是围绕几个问题,并不是对流程进行系统分析,回头有机会再对流程进行分析。







1.整体流程

本文主要针对以下代码分析,重点分析getSqlSession和sqlsession.commit方法。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取sqlsession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          //提交sqlsession
          sqlSession.commit(true);
        }
        return result;
    }
  }







2.sqlsession的获取

在获取sqlsession的时候,首先尝试从事务管理器中获取,获取到了直接返回,获取不到就新建一个sqlsession。DefaultSqlSession本身不是线程安全的,但是在与spring结合之后,能够保证不同的事务中使用不同的sqlsession,从而保证线程安全性,但同时也有一个问题,如果在事务中使用多线程,会导致事务失效。

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

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    //在TransactionSynchronizationManager中获取session
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

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

    LOGGER.debug(() -> "Creating a new SqlSession");
    //获取不到session时新创建session
    session = sessionFactory.openSession(executorType);
    //如果存在事务,将当前的sessionFactory作为key注册到TransactionSynchronizationManager中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

3.sqlsession的提交方法

commit方法中主要是对缓存进行操作,在没有事务的情况下,还会调用connection的commit方法进行提交。

   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 commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    //把本地缓存刷新到二级缓存。
    flushPendingEntries();
    reset();
  }

在没有事务的时候,会直接调用commit方法,进行缓存的相关操作;在有事务的情况下,会在triggerBeforeCommit方法中调用commit方法,由于事务是一个整体,所以需要在事务执行完成后头统一提交,而在前面获取sqlsession的过程也保证了一个事务的执行过程中sqlsession是统一的,这样能够保证在事务执行完成之后同意更新一级缓存和二级缓存,避免出现事务没有提交就更新缓存的情况,或者事务回滚导致缓存出现脏数据的情况。

	private void processCommit(DefaultTransactionStatus status) throws TransactionException {
		try {
			boolean beforeCompletionInvoked = false;

			try {
				boolean unexpectedRollback = false;
				prepareForCommit(status);
				triggerBeforeCommit(status);
				triggerBeforeCompletion(status);
				beforeCompletionInvoked = true;
			}
	}

三、SqlSessionSynchronization

    spring在事务提交的时候直接通过connection的commit方法提交,回滚的时候通过connection的rollback方法回滚,同时mybatis中的Executor接口也有commit和rollback方法,而且commit方法还会将一级缓存中的内容提交到二级缓存。

       spring事物中的提交方法是如何与mybatis的提交方法相关联,主要是通过SqlSessionUtils类中的内部静态类SqlSessionSynchronization。

       SqlSessionSynchronization间接实现了TransactionSynchronization接口,能够在spring事物执行的过程中穿插一些mybatis的方法,从而实现spring事务与mybatis的处理之间的联系,其实还是Spring开放的接口牛逼。

  private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {}
  public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered {}

           Spring事务的processCommit方法中的triggerBeforeCommit方法会调用所有TransactionSynchronization的beforeCommit方法,注意这里是指当前线程本地所有的,记录在事务线程本地的。在beforeCommit方法中,调用了sqlSession的commit方法,间接调用了Executor的commit方法。实现了将一级缓存中的内容刷新到二级缓存。

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

          在beforeCompletion方法中将当前事务关联的sqlSession关闭。

    public void beforeCompletion() {
      // Issue #18 Close SqlSession and deregister it now
      // because afterCompletion may be called from a different thread
      if (!this.holder.isOpen()) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
        }
        TransactionSynchronizationManager.unbindResource(sessionFactory);
        this.holderActive = false;
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
        }
        this.holder.getSqlSession().close();
      }
    }

         在afterCompletion方法中将SqlSessionHolder重置。 

    public void afterCompletion(int status) {
      if (this.holderActive) {
        // afterCompletion may have been called from a different thread
        // so avoid failing if there is nothing in this one
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
        }
        TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
        this.holderActive = false;
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
        }
        this.holder.getSqlSession().close();
      }
      this.holder.reset();
    }



       比较有意思的是在SqlSessionSynchronization类中并没有rollback相关的单独处理,想想确实回滚就相当于当前操作没有做过,由于spring与mybatis结合之后一级缓存失效,也不需要清除一级缓存。


总结

      本文主要对connection和sqlsession的获取过程进行分析,分析了sqlsesssion的线程安全性,有事务的情况下sqlsession的一致性及其原因和mybatis和sqlsession的connection的一致性,相对比较简单,有机会还是要把主流程分析一下。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值