Mybatis SqlSessionTemplate 源码解析

时间:2022-04-26
本文章向大家介绍Mybatis SqlSessionTemplate 源码解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

来源:博客园-大新博客

链接:http://www.cnblogs.com/daxin/p/3544188.html(点击文末阅读原文前往)

在使用Mybatis与Spring集成的时候我们用到了 SqlSessionTemplate 这个类。

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
          <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

通过源码我们何以看到 SqlSessionTemplate 实现了SqlSession接口,也就是说我们可以使用 SqlSessionTemplate 来代理以往的 DefailtSqlSession完成对数据库的操作,但是 DefailtSqlSession这个类不是线程安全的,所以这个类不可以被设置成单例模式的。

如果是常规开发模式 我们每次在使用 DefailtSqlSession的时候都从 SqlSessionFactory当中获取一个就可以了。但是与Spring集成以后,Spring提供了一个全局唯一的SqlSessionTemplate示例 来完成 DefailtSqlSession的功能,问题就是:无论是多个dao使用一个 SqlSessionTemplate,还是一个dao使用一个 SqlSessionTemplate,SqlSessionTemplate都是对应一个 sqlSession,当多个web线程调用同一个dao时,它们使用的是同一个 SqlSessionTemplate,也就是同一个 SqlSession,那么它是如何确保线程安全的呢?让我们一起来分析一下。

(1)首先,通过如下代码创建代理类,表示创建 SqlSessionFactory的代理类的实例,该代理类实现 SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现 SqlSession接口定义的方法,该调用则被导向 SqlSessionInterceptor的invoke方法

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

核心代码就在 SqlSessionInterceptor的invoke方法当中。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
      //这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
      //一会我们在分析这个方法
      final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //调用真实SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        //然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        //返回执行结果
        return result;
      } catch (Throwable t) {
        //如果出现异常则根据情况转换后抛出
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭sqlSession
        //它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动作
        //如果sqlSession被Spring管理 则调用holder.released(); 使计数器-1
        //否则才真正的关闭sqlSession
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }

在上面的invoke方法当中使用了俩个工具方法 分别是

SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)

SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)

那么这个俩个方法又是如何与Spring的事物进行关联的呢?

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {     
    //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式 
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
 //如果holder不为空,且和当前事务同步 
    if (holder != null && holder.isSynchronizedWithTransaction()) { 
      //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用 
      if (holder.getExecutorType() != executorType) { 
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 
      } 
      //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加 
      holder.requested(); 
   //返回sqlSession 
      return holder.getSqlSession(); 
    } 
 //如果找不到,则根据执行类型构造一个新的sqlSession 
    SqlSession session = sessionFactory.openSession(executorType); 
 //判断同步是否激活,只要SpringTX被激活,就是true 
    if (isSynchronizationActive()) { 
   //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务 
      Environment environment = sessionFactory.getConfiguration().getEnvironment(); 
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 
  //如果是,则将sqlSession加载进事务管理的本地线程缓存中 
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
  //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 
        bindResource(sessionFactory, holder); 
  //将holder, sessionFactory的同步加入本地线程缓存中 
        registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 
        //设置当前holder和当前事务同步 
  holder.setSynchronizedWithTransaction(true); 
  //增加引用数 
        holder.requested(); 
      } else { 
        if (getResource(environment.getDataSource()) == null) { 
        } else { 
          throw new TransientDataAccessResourceException( 
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 
        } 
      } 
    } else { 
    } 
    return session; 
  }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 
 //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder  
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
    if ((holder != null) && (holder.getSqlSession() == session)) { 
   //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 
      holder.released(); 
    } else { 
   //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close 
      session.close(); 
    } 
  }

其实通过上面的代码我们可以看出 Mybatis在很多地方都用到了代理模式,这个模式可以说是一种经典模式,其实不紧紧在Mybatis当中使用广泛,Spring的事物,AOP ,连接池技术 等技术都使用了代理技术。在后面的文章中我们来分析Spring的抽象事物管理机制。

小编语:

下面文章有助于您更深入地理解:

https://my.oschina.net/realfighter/blog/366089

ThreadLocal与Spring 事务管理