我们知道Mybatis最终是通过SqlSession对象去执行sql语句的,通过前面几篇文章我们也知道Mybatis是怎么解析mapper.xml文件、怎么把sql语句读入到Mybatis的Congifuration对象中、最后Mapper接口的代理对象是怎么匹配到sql语句、并最终通过SqlSession对象去执行sql语句的。

上述整个过程我们都已经了解过了,唯一一个尚未分析过的问题是:SqlSession是怎么初始化的?以及他具体是怎么执行sql语句的?

今天的主要目标就是要搞清楚以上两个问题:

  1. SqlSession对象的创建或者初始化过程。
  2. SqlSession执行sql语句的具体过程。

SqlSession对象的创建

不同的项目,SqlSession会有不同的创建过程,但基本都大同小异。我们今天主要分析Springboot+Mybatis项目中SqlSession的创建过程。

Springboot项目中主要通过配置类MybatisAutoConfiguration来完成Mybatis的初始化。

MybatisAutoConfiguration主要完成:

  1. 创建并初始化Configuration对象
  2. 通过SqlSessionFactoryBean初始化SqlSessionFactory(初始化为DefaultSqlSessionFactory)
  3. 初始化SqlSession,Spring项目SqlSession初始化为SqlSessionTemplate对象

MybatisAutoConfiguration#sqlSessionFactory

sqlSessionFactory方法主要完成Configuration对象的创建,以及SqlSessionFactory对象的创建。方法比较长,最后通过SqlSessionFactoryBean的build方法创建DefaultSqlSessionFactory对象返回,并交给Spring Ioc容器管理。

@Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
//省略
return factory.getObject();

MybatisAutoConfiguration#sqlSessionTemplate

sqlSessionTemplate对象是Spring+Mybatis项目的SqlSession落地对象,通过sqlSessionTemplate可以实现Spring和Mybatis的无缝对接。

通过上面SqlSession的类结构,我们知道SqlSessionTemplate实际是SqlSession的一个实现类,所以,通过@Bean注解的最终返回SqlSessionTemplate对象的方法sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)的目的就是创建SqlSession对象,并最终交给Spring的Ioc容器来管理。其中参数SqlSessionFactory是通过Spring Ioc依赖注入的DefaultSqlSessionFactory对象:

@Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

最终会调用到SqlSessionTemplate的SqlSessionTemplate方法:

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

我们需要把关注点放在代码最后一句话,创建了一个动态代理对象(又是动态代理)赋值给SqlSessionTelmplate对象的sqlSessionProxy,这个代理对象的回调对象为SqlSessionInterceptor,我们对JDK动态代理已经非常熟悉了,我们知道所有的对于代理对象sqlSessionProxy的方法调用,最终都会调用到回调对象SqlSessionInterceptor的invoke方法。

好了,到这里我们就知道Mybatis的最重要的一个对象SqlSession被创建好了,他其实就是SqlSessionTelmplate对象,他的两个重要属性我们也要搞清楚:

  1. sqlSessionFactory:其实就是通过参数传入进来的DefaultSqlSessionFatcory对象。
  2. sqlSessionProxy:其实就是以SqlSessionInterceptor为回调对象的JDK动态代理对象。

SqlSession执行sql语句的具体过程

这个问题其实上一篇文章已经分析过了,通过上一篇文章其实我们已经知道了mapper.xml文件中的sql语句是怎么被解析到Configuration对象的mappedStatements中,并且也明白了mapper接口中的方法最终怎么匹配到Configuration对象中的mappedStatements并最终执行其中的sql语句。

整个过程我们已经一清二楚了,只不过,最终sql语句执行的时候是通过sqlSession对象来执行的,上一篇文章并没有详细分析sqlSession执行sql语句的具体过程。我们现在来具体分析一下这一部分。

通过前面的分析,现在我们知道sqlSession对象其实是SqlSessionTelmplate对象,我们就以selectOne方法为例,直接看一下SqlSessionTelmplate的selectOne方法。

我们知道mapper.xml文件中的select语句的返回类型如果不是一个list、而是一个实体对象的时候,比如selectXXXById通过主键键值返回一条记录的时候,最终会匹配到这个selectOne方法。

看一下SqlSessionTelmplate源码,他交给sqlSessionProxy去处理,其实我们可以看到SqlSessionTelmplate的所有的sql语句执行的方法最终都是委托给sqlSessionProxy去处理的。

我们已经知道这个sqlSessionProxy其实就是动态代理对象,他最终会调用到回调对象SqlSessionInterceptor的invoke方法。

 @Override
  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
  }

SqlSessionInterceptor#invoke

方法源码不太长,我们先把源码都贴出来:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

首先通过SqlSessionUtils的getSqlSessio去获取Sqlsession,Mybatis其实就是通过这个SqlSessionUtils实现与Spring的sqlSession做集成的,他首先去获取TransactionSynchronizationManager中的sqlSession,如果TransactionSynchronizationManager中存在当前线程的sqlSession的话则直接返回,不存在的话再通过DefaultSqlSessionFactory去创建sqlSession返回并存入TransactionSynchronizationManager中。

DefaultSqlSessionFactory的创建sqlSession的代码我们前面其实已经看过了,比如他的openSessionFromDataSource方法,最终返回的是DefaultSqlSession对象,所以真正的SqlSession其实是DefaultSqlSession对象。

获取到DefaultSqlSession对象后,执行:

 Object result = method.invoke(sqlSession, args);

这句代码的意思是,通过反射机制执行sqlSession(也就是DefaultSqlSession对象)的selectOne方法。

DefaultSqlSession的selectOne方法通过获取到的数据库connection、以及事务管理等相关参数执行sql语句,这部分我们在前面的文章中也分析过了。

从源码中我们可以看到SqlSession最终通过JDK动态代理的方式创建代理对象执行sql语句、而不是直接创建一个sqlSession对象去执行的目的,可能就是为了通过AOP的方式在sql语句执行完成后做一些必要的善后处理,也就是上面invoke方法中method.invoke方法执行后的commit以及closeSqlSession等相关逻辑,这样的话调用方、也就是应用层就不需要处理这些善后工作了。

以上!

上一篇 Mybatis的Mapper代理对象生成及调用过程
下一篇 Mybatis拦截器顺序


45 声望17 粉丝