水煮MyBatis(二十)- 事务之内共享SqlSession

193 阅读2分钟

前言

今天更新一个小知识点,也是初学者比较容易遗漏的问题,那就是如果开启事务,sqlSession在事务周期之内,都是共享的,方便批量提交和回滚,实现数据一致性。

涉及到的序列图

image.png

关键步骤

  • 前提:方法或者类上,开启事务;
  • 在SqlSessionHolder里获取缓存的sqlSession时,如果返回空,则需要新建sqlSession或者注册holder;
  • 用上一步新建的sqlSession,作为构造器参数,新建holder;
  • 后续查询,直接从holder返回sqlSession,实现事务周期内共享;

源码说明

获取sqlSession

这里主要是看SqlSessionUtils里的getSqlSession方法

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    // 获取事务内的sqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    // 从holder里获取sqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      // 如果有缓存,则直接共享
      return session;
    }
    // 如果session为空,则新建
    session = sessionFactory.openSession(executorType);
    // 并将session作为参数,新建holder
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
  }

注意这一行:TransactionSynchronizationManager.getResource(sessionFactory);是从resource的map集合中,获取holder,后续注册的时候,也是以sessionFactory为key,将holder作为value保存到事务上下文的resource中。

从holder里获取sqlSession

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
    SqlSession session = null;
    if (holder != null && holder.isSynchronizedWithTransaction()) {
      // 引用数 +1
      holder.requested();
      // 获取sqlSession
      session = holder.getSqlSession();
    }
    return session;
  }

方法代码很少,主要是判空和isSynchronizedWithTransaction判定,但SynchronizedWithTransaction都是被设置为true的,基本上可以忽略不计,毕竟事务内执行,几乎都是需要同步执行的。下面是设置SynchronizedWithTransaction属性的地方,没有为false的,除非是用自定义插件进行修改。
image.png

注册SessionHolder

如果从holder里获取不到sqlSession,那么可以肯定的是,holder也是null,需要新建。

  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    // 是否开启事务
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();
      // 是否为spring事务
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
       // 新建holder,sqlSession作为构造参数
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        // 设定键值对映射,后续获取holder时,执行Manager.getResource(sessionFactory)方法;
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        // 请求次数+1
        holder.requested();
      } 
    } 
  }

步骤:

  • 前提条件是开启事务,在TransactionManager.startTransaction中设置此状态位;
  • 新建holder,sqlSession作为构造参数
  • 设定键值对映射,后续获取holder时,执行Manager.getResource(sessionFactory)方法;
  • holder请求次数+1;

销毁holder

众所周知,sqlSession在事务关闭时销毁,那么SqlSessionHolder是否在同一时间节点销毁呢?看一下源码:

  public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
   // 获取事务中的holder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
      // 如果holder满足条件,则引用次数-1
      holder.released();
    } else {
      // 否则,直接关闭sqlSession,一般情况下,没有开启事务的,都会走这个逻辑分支的
      session.close();
    }
  }

步骤:

  • 获取注册在事务中的holder;
  • 如果holder满足条件,则引用次数 -1;
  • 否则,直接关闭sqlSession,一般情况下,没有开启事务的,都会走这个逻辑分支的

那什么时候销毁holder呢?其实是在事务完成以后,TransactionManager.doCleanupAfterCompletion方法中执行。