阅读 903

Mybatis源码之SQL执行过程

公众号搜索“码路印记”,点关注不迷路!

日常开发使用mybatis进行CURD操作十分简便,我们只需要在Mapper接口定义好方法,然后在mapper.xml中写好SQL语句,就能在业务代码中使用了。这简单到令人发指步骤,让我们麻痹到以为这个过程是非常简单的,时间久了脑子都笨了。今天,接着上一篇文章中的例子,结合源码学习一下mybatis中一条SQL的执行过程,了解下背后发生的故事。

从Demo开始

还是之前的例子,代码如下。

示例代码

<!--CompanyDAO.xml-->
<select id="selectById" resultMap="baseResultMap" >
    select
    <include refid="BaseColumns"></include>
    from company
    where id= #{id}
</select>
复制代码

CompanyDAO.xml,按照id查询数据表company的记录,id为主键,selectById为statementId。

public interface CompanyDao {
    /**
     * 根据id查询CompanyDO
     *
     * @param id 主键id
     * @return 查询结果
     */
    CompanyDO selectById(@Param("id") Integer id);
}
复制代码

CompanyDao.java,Mapper接口,仅包含一个方法selectById,按照mybatis要求,与mapper的xml配置文件一一对应。

public class Program {
    public static void main(String[] args) throws IOException {
        // mybatis配置文件
        String path = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(path);
        // 获取 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 创建 SqlSession
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            //获取Mapper:CompanyDao
            CompanyDao dao = sqlSession.getMapper(CompanyDao.class);
            //调用selectById方法
            CompanyDO companyDO = dao.selectById(1);
            System.out.println(companyDO);
        }
    }
}
复制代码

Program.java业务代码,调用逻辑比较简单,再啰嗦一下:

  • 通过mybatis-config.xml创建SqlSessionFactory;
  • 使用SqlSessionFactory创建并打开SqlSession;
  • 通过sqlSession获取CompanyDao的Mapper实例对象;
  • 调用selectById方法获取查询结果;

示例分析

由以上示例代码可知,CompanyDao是一个接口,我们并没有编写它的实现类,那为啥可以创建它的对象呢?我们现在得到的到底是哪个类的实例?

打个断点!下面调试界面截图,我们发现dao是org.apache.ibatis.binding.MapperProxy的实例。
image.png
熟悉动态代理的同学,看一眼就知道MapperProxy是一个代理类。我们就从这里入手,顺藤摸瓜。

流程分析

Mapper对象怎么来的?

MapperProxy对象是通过SqlSession#getMapper方法创建的,这里的sqlSession是DefaultSqlSession的实例,跟着源码走一下。

  //org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
  @Override
  public <T> T getMapper(Class<T> type) {
    //这里调用了configuration的方法,继续走
    return configuration.<T>getMapper(type, this);
  }

  //org.apache.ibatis.session.Configuration#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //所有的Mapper都注册在Configuration#mapperRegistry(不过是存储的MapperProxyFactory),所以是通过它来获取Mapper对象
    //下一步进入MapperRegistry#getMapper
    return mapperRegistry.getMapper(type, sqlSession);
  }

  //org.apache.ibatis.binding.MapperRegistry#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //通过type从map中获取MapperProxyFactory实例
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    //如果mapperProxyFactory,则抛出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //通过mapperProxyFactory的newInstance创建实例,继续走
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

  //org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
  public T newInstance(SqlSession sqlSession) {
    //先创建了MapperProxy对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //调用了newInstance方法
    return newInstance(mapperProxy);
  }

  //org.apache.ibatis.binding.MapperProxyFactory#newInstance
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //到这里就揭开了代理对象创建的面纱
    //使用Proxy创建了代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
复制代码

为了方便梳理,上面把Mapper创建过程的代码放在一起了,我们可以发现整个过程是通过Configuration串在一起的(Configuration是mybatis运行时的基础)。简单描述一下过程:

  • 入口在DefaultSqlSession#getMapper,整个方式只是一个中继,需要通过Configuration#getMapper来创建;
  • 在解析mybatis配置时,所有的Mapper注册信息由Configuration#mapperRegistry存储,但是存储的其实是一个与Mapper类型相关的MapperProxyFactory,既然是Factory,那就要生产点啥了。
  • MapperProxyFactory创建了MapperProxy对象,然后通过Proxy创建了代理对象,由MapperProxy伪装了一个Mapper实例。


所以,Mapper对象(示例中的dao)其实一个“披着Mapper皮的MapperProxy对象”。上述过程虽然跳转的类比较多,一层一层走下来最主要的就是Java动态代理相关的内容。通过下面的时序图再来看下Mapper对象的创建过程吧。
image.png

代理对象invoke方法执行

创建Mapper对象后,接下来调用Mapper方法,从下面的代码开始:

CompanyDO companyDO = dao.selectById(1);
复制代码

dao为MapperProxy对象,当调用selectById时,会先调用org.apache.ibatis.binding.MapperProxy#invoke方法(这里可以看下Java动态代理相关的文章,保存出代理类的动态实现,有机会写篇文章)。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断是否为Object类
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } 
      //判断是否为默认方法  
      else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //走到这里说明是被代理接口的方法,获取或设置MapperMethod缓存,得到MapperMethod对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //方法执行
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    //如果缓存中不存在method,就创建一个对象添加到缓存。
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
复制代码

到这里,我们可以知道方法invoke最终执行的是org.apache.ibatis.binding.MapperMethod#execute,接着看MapperMethod这个类。

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    //sql命令,包含mapper接口、mapper方法和configuration
    this.command = new SqlCommand(config, mapperInterface, method);
    //方法签名
    this.method = new MethodSignature(config, mapperInterface, method);
  }
}
复制代码

MapperMethod构造方法内步创建了SqlCommand和MethodSignature,这两个类都是MapperMethod的内部类。等下看org.apache.ibatis.binding.MapperMethod#execute时会发现,它的执行与这两个类有很大关系,我们先来看下这两个做了什么。

  public static class SqlCommand {

    private final String name;
    //sql命令类型枚举:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
    private final SqlCommandType type;

    //构造方法:主要逻辑
    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      //获取方法名称:示例中就是:selectById
      final String methodName = method.getName();
      //方法所在类的全路径限定名称:com.raysonxin.dao.CompanyDao
      final Class<?> declaringClass = method.getDeclaringClass();
      //解析获取MappedStatement:看下面方法的注释。
      //这样就可以拿到MappedStatement对象了
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        //获取id
        name = ms.getId();
        //获取命令类型
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }

    //解析获取MappedStatement
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      //拼接获取statement,示例中是:com.raysonxin.dao.CompanyDao.selectById
      //这其实就是Configuration中MappedStatement注册的key
      String statementId = mapperInterface.getName() + "." + methodName;
      //判断configuration是否存在这个statementId
      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;
    }
  }

复制代码

SqlCommand就是根据接口和方法的签名信息,从Configuration中获取对应的MappedStatement,MappedStatement是在解析mybatis-config.xml是添加的,大家可以翻阅Configuration解析代码,或者查看上一篇文章《Mybatis源码之Configuration》。SqlCommand创建后,我们就可以清楚知道了其对应的MappedStatement,也就知道了它是查询、更新、删除还是添加这样的命令类型。

  public static class MethodSignature {

    //返回结果是否为多个值,即列表
    private final boolean returnsMany;
    //返回结果是否为Map类型
    private final boolean returnsMap;
    //返回结果是否为void
    private final boolean returnsVoid;
    //返回结果是否为游标
    private final boolean returnsCursor;
    //返回结果是否为Optioanl类型
    private final boolean returnsOptional;
    //返回值类型
    private final Class<?> returnType;
    private final String mapKey;
    //
    private final Integer resultHandlerIndex;
    //查询数据范围参数索引
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;

    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }
 }
复制代码

MethodSignature是通过反射获取其方法描述信息,也就是签名,代码太多,仅通过其字段和构造方法看下吧。MethodSignature主要的逻辑是获取其返回值类型,比如单个、多个、Map等。

对SqlCommand和MethodSignature两个内部类有一定的认识后,我们继续看下MapperMethod#execute方法。execute方法会根据SqlCommandType处理不同的命令:

  • INSERT、UPDATE、DELETE的处理方式应该说是基本相同,参数转换后调用对应的SqlSession方法去执行;
  • 对于SELECT命令,需要根据返回值特点,转换参数、处理查询范围等,然后调用SqlSession不同的查询方法;
public class MapperMethod {

  //...

  //方法执行逻辑
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断命令类型:间接从MappedStatement获取的
    switch (command.getType()) {
      //insert数据
      //INSERT、UPDATE、DELETE的处理方式一毛一样的;
      //1、参数处理:把参数列表转为Map结构,同时会复制一份参数按照param0、param1这样put到Map
      //2、调用SqlSession对应的方法执行命令,command.getName()就是我们示例中的com.raysonxin.dao.CompanyDao.selectById
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //更新数据
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //删除数据
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //查询命令:会根据返回值的类型调用不同的处理方法,进而路由到SqlSession不同的查询方法,在此之前需要做一些准备工作:
      //selectOne、selectList、selectCursor等
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
    
  //...
}
复制代码

MapperMethod#execute最终调用了SqlSession接口的方法,在我们的示例中会调用SqlSession#selectOne。
image.png
走到这里是不是有点懵了,怎么调来调去,绕了一圈又回到SqlSession了?为啥不直接调用SqlSession接口呢?这里插一句:直接使用SqlSession确实是可以,而且也是早起Mybatis所支持的方式;但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用MyBatis 支持接口(Interface)调用方式,即Mapper接口。

这一段过程的主要工作就是:采用动态代理方法,根据我们所调用的接口和方法,结合Configuration所加载的配置信息,动态的把请求路由到SqlSession对应的接口上来。
image.png_

Sql命令执行

绕了一圈回到SqlSession后,才算是真正开始sql命令语句的执行。这个过程也是很复杂的,需要依次经过SqlSession、Executor、StatementHandler、ParamterHandler、ResultSetHandler等,流程较长,不能面面俱到,我们随着代码走一遍流程,然后通过后续的文章逐个补齐细节。先看个大体的流程图:
image.png

SqlSession

按照示例,DefaultSqlSession为具体实现,从DefaultSqlSession#selectOne()开始。

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //调用selectList方法,获取返回结果list
    List<T> list = this.selectList(statement, parameter);
    //如果结果刚好是1个,返回;若为多个,抛出异常;若为空,返回null;
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    //中转一下,RowBounds给了默认值,不设限。
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //从configuration获取MappedStatement,
      //示例中statement=com.raysonxin.dao.CompanyDao.selectById
      MappedStatement ms = configuration.getMappedStatement(statement);
      //通过执行器Executor执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
复制代码

根据DefaultSqlSession代码走下来,从selectOne走到selectList,在selectList中调用了Executor#query方法。根据Executor的原理,当前executor为CachingExecutor实例,并且SimpleExecutor为其代理执行器(如下图),所以接下来进入Executor执行器的流程。
image.png

Executor

CachingExecutor是Executor的缓存设计,若缓存中存在所要查询的内容会直接返回,否则会交给其代理执行器通过数据库进行查询。从CachingExecutor#query(MappedStatement, Object, RowBounds, ResultHandler)开始:

 //org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //获取缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //调用重载方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  //org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //查询缓存
    Cache cache = ms.getCache();
    //缓存存在
    if (cache != null) {
      //如果需要刷新缓存
      flushCacheIfRequired(ms);
      //如果启用缓存
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        //数据为空还是要调用委托执行器的query
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //缓存不存在时,调用委托执行器的query
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
复制代码

我们看到如果未启用或者未命中缓存的情况,CachingExecutor会通过其委托执行器继续查询,也就是SimpleExecutor#query。SimpleExecutor继承自BaseExecutor,在BaseExecutor也同样具有缓存的处理逻辑,我们暂时跳过,仅贴出主要代码:

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //省略。。。
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //调用方法从数据库查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    //....
}

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //执行doQuery,这个是抽象方法,具体实现在SimpleExecutor
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      //获取configuration
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler,默认为PreparedStatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //创建并初始化statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //由StatementHandler执行查询,使用ResultSetHandler处理查询结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //创建Statement,BaseStatementHandler#prepare
    stmt = handler.prepare(connection, transaction.getTimeout());
    //设置参数,使用ParameterHandler
    handler.parameterize(stmt);
    return stmt;
  }
复制代码

Executor过程的执行就到这里了,CachingExecutor->BaseExecutor->SimpleExecutor,并且完成了StatementHandler执行的准备工作。CachingExecutor充当了缓存一层,当未命中缓存时会执行数据库查询,此时交给BaseExecutor及其子类执行。我们通过doQuery方法看到了StatementHandler创建、参数设置、执行与关闭整个生命周期,可以说Executor是StatementHandler的调度者。

从SimpleExecutor#doQuery、SimpleExecutor#prepareStatement方法看,可以把StatementHandler的工作分为几个过程:创建Statement对象、设置Statement参数、执行Statement查询并返回结果、关闭Statement对象。其中“设置Statement参数”需要使用ParameterHandler进行参数设置、“执行Statement查询并返回结果”需要使用ResultSetHandler处理查询结果。接下来重点看下参数设置、查询、结果处理三个过程。

ParameterHandler

prepareStatement方法中,调用了StatementHandler#parameterize进行参数设置,我们看下PreparedStatementHandler的实现。

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
复制代码

这里的parameterHandler是ParameterHandler实例。在mybatis中,ParameterHandler仅有一个默认实现,即:DefaultParameterHandler。所以,直接看DefaultParameterHandler#setParameters方法:

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //获取参数映射列表,它来自SqlSource的解析过程,目前了解即可。
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      //遍历参数映射
      for (int i = 0; i < parameterMappings.size(); i++) {
        //获取当前参数映射对象
        ParameterMapping parameterMapping = parameterMappings.get(i);
        //如果参数模式不是OUT,即IN、INOUT时,继续执行
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          //获取参数名称:示例中为:id
          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;
          } 
          //类型处理器是否存在当前参数类型。如果只有一个参数,这里一般会命中。
          //我们编写sql语句时,没有标明入参类型,mybatis会把参数类型解析为Object
          else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } 
          //多个参数时,会走到这里。  
          else {
            //转为MetaObject类似于一个map
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            //获取当前参数对应的值
            value = metaObject.getValue(propertyName);
          }
          //获取类型处理器
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            //通过类型处理器设置Statement参数。
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

复制代码

setParameters设置参数的过程比较简单:获取参数映射列表后,依次遍历参数映射对象,逐个参数类型获取类型处理器,然后使用类型处理器设置Statement参数。当然,这里面会设置到类型处理器的查询中转操作(UnKnown->Integer),大家可以调试看下过程。

StatementHandler

参数设置完成后,就到了执行操作了。示例中将会走到PreparedStatementHandler#query,如下:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //转为PreparedStatement类型
    PreparedStatement ps = (PreparedStatement) statement;
    //执行
    ps.execute();
    //处理查询结果
    return resultSetHandler.handleResultSets(ps);
  }
复制代码

呃,这个太简洁了。把statement类型转为PreparedStatement,执行execute,处理查询结果并返回。Statement相关的内容属于传统JDBC范畴了,大家比较熟悉,不再赘述。

ResultSetHandler

这应该是查询过程中的最后一步了!由PreparedStatementHandler#query方法可知,执行java.sql.PreparedStatement#execute后,mybatis使用ResultSetHandler#handleResultSets处理查询结果。mybatis对ResultSetHandler也仅有一个默认实现DefaultResultSetHandler,直接看代码吧:

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //获取第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    //获取结果映射
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //如果rsw为空,或者resultMapCount<1,会抛出异常
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      //获取结果集映射对象
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //获取结果,完成查询结果映射
      handleResultSet(rsw, resultMap, multipleResults, null);
      //处理下一个,多个结果时有用
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    //获取配置的结果集信息,我们没有使用,不用管了。
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
复制代码


总结一下这个阶段的处理过程,看图吧,一图胜千言!
image.png

总结

本文从一个简答的Mapper查询方法开始,详细分析了其执行过程。按照我的个人理解,把执行过程分为了三个阶段:

  • Mapper接口实例的动态实现阶段:Mybatis基于动态代理,动态实现Mapper接口,其实一个“披着Mapper皮的MapperProxy对象”;
  • Mapper接口命令解析阶段:这个过程中基于接口、方法签名,结合反射技术、Configuration配置信息,确认了对应的SqlSession方法入口。
  • SQL命令执行阶段:从SqlSession入口开始,依次经过Executor、StatementHandler、ParameterHandler、ResultSetHandler四大组件的层层处理,最终完成查询过程。


好了,文章到这里就结束了!由于本人水平有限,如有错误、不实之处,欢迎大家批评指正!

公众号搜索“码路印记”,点关注不迷路!

文章分类
后端
文章标签