Mybatis 执行Sql流程

前言

对于mybatis之前已经讲了mybatis 中接口注入spring源码分析,mybatis 接口依赖注入源码分析。现在mybatis的接口能够放入Spring,并且能够依赖注入,这一节讲的重点就是以下代码的具体流程。

    @Autowired
    private CardMapper cardMapper;
    public void select() {
        cardMapper.selectCardById(1);
    }

重点关注类

MapperProxy:
SqlSessionTemplate:
SqlSessionUtils:
SqlSessionFactory:
Configuration:
CachingExecutor:
Executor:
SqlSession:
org.apache.ibatis.transaction.Transaction:
Connection:
RoutingStatementHandler:
StatementHandler:
Statement:
ResultSetHandler

一创建SqlSession

SqlSession用于对外执行sql,并且一般来说SqlSession使用了之后就要关闭掉,防止内存泄漏。既然说的是执行sql流程,那么SqlSession的创建就一定是避不开的。通常我们都会结合Spring一起使用mybatis,并且SqlSession的创建,关闭、提交、回滚都交给Spring管理,对于使用者来说完全无感知。就如下面这段代码,执行Sql的时候,用户完全不需要关心SqlSession的管理。这里SqlSession的创建我们具体指SqlSessionTemplateDefaultSqlSession

 @Autowired
    private CardMapper cardMapper;
    public void select() {
        cardMapper.selectCardById(1);
    }

1.1 创建SqlSessionTemplate

SqlSessionTemplateSpring用来管理SqlSession的生命周期的,并且这个类是全局唯一的,线程安全的,在项目启动的时候就会创建,创建后最后会赋值到MapperProxy中。先简单的看下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());
  }
  @Override
  public  T selectOne(String statement) {
//    使用代理查询
    return this.sqlSessionProxy. selectOne(statement);
  }

SqlSessionInterceptorSqlSessionTemplate内部类,并且实现了InvocationHandler接口,用于代理SqlSession,所以只要是sqlSessionProxy调用了SqlSession的方法都会调用SqlSessionInterceptor中的invoke方法。具体的invoke方法我会在下面讲解。这里先贴下创建SqlSessionTemplate的代码,该代码存在于mybatis.spring.boot.autoconfigure包下。

//MybatisAutoConfiguration
 @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);
    }
  }

1.2 创建DefaultSqlSession

Mybatis 执行Sql流程_第1张图片
创建DefaultSqlSession的流程图

)
创建DefaultSqlSession的过程首先是调用接口的xxx方法(cardMapper.selectCardById(1)),接着就是MapperProxy对象的invoke方法其实在mybatis 接口依赖注入源码分析就简单的展示了MapperProxy的源码,其实Spring帮我们的接口依赖注入对象就是MapperProxy对象。当我们调用接口中的xxx方法就直接调用代理类MapperProxy中的invoke方法。

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   ....
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // sqlSession 就是SqlSessionTemplate,执行sqlSessionTemplate中对应的方法。
    return mapperMethod.execute(sqlSession, args);
  }

SqlSessionTemplate调用select方法的时候,就是sqlSessionProxy调用对应的方法,接着就是调用SqlSessionInterceptorinvoke方法。
在上没有展示SqlSessionInterceptorinvoke方法,这里讲下。以下段代码主要分三部分。

  1. 创建SqlSession
  2. 执行SqlSession对应的方法。
  3. 关闭SqlSession
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) {
      
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

二、获取SQL

在mybatis 解析xml流程中介绍了类似等节点,ms.getId()是接口中的包名+类名+方法名(test.CardMapper.select)。因此现在SQL是以MappedStatement形式存在ConfigurationMap中,key为包名+类名+方法名,所以只要知道执行方法的key(包名+类名+方法名)就可以获取SQL

#Configuration
 protected final Map mappedStatements = new StrictMap("xxxxx");

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

其实当你调用接口的某个方法的时候就已经知道这个key(包名+类名+方法名)是什么了,以下是mybatis组装key的代码。

 private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName,
        Class declaringClass, Configuration configuration) {
        //组装id key    
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

以下是DefaultSqlSession执行查询代码,具体解释见代码。

@Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
    
      //从map中获取MappedStatement(
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清除缓存
      clearLocalCache();
    }
    List list;
    try {
      queryStack++;
      // 查询缓存
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

继续添加代码流程。 private final MappedStatement mappedStatement; // xxxMapper.select()的入参对象 private final Object parameterObject; // private final BoundSql boundSql; private final Configuration configuration; @Override public void setParameters(PreparedStatement ps) { // 入参都会按照sql的参数顺序一一的放入parameterMappings 里面。 List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 获取参数的类型处理器,比如string 对应StringTypeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { // 设置参数 typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { } catch (SQLException e) { } } } } }

五、执行SQL

执行SQL是通过StatementHandler执行,当然这是个接口,具体的操作由子类去实现。

Mybatis 执行Sql流程_第6张图片
image.png

RoutingStatementHandler: 仅仅是选择让哪个StatementHandler去执行SQL,下面是代码是RoutingStatementHandler的构造函数,里面根据StatementType选择不同的StatementHandler,而StatementType在写