Mybatis源码分析之Select返回数据分析

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析之Select返回数据分析相关的知识,希望对你有一定的参考价值。

Mybatis源码分析之Select返回数据分析

在之前的一篇文章中分析了@Select注解的使用方法,在查询方法中我们知可以返回Map类型,也可以返回指针,或者是list集合,或是单条记录,今天就对这几种返回做一个源码分析。

Select查询


在这里就不需要再写一个demo演示了,用法无非这么多,直接看查询的源码吧。

首先在看对应select的查询源码时先看下MyBatis是怎么定义method的返回类型的,这里在将method包装成MappedMethod时可以得到对应返回类型。

//返回的MapperProxy代理对象中包装method
final MapperMethod mapperMethod = cachedMapperMethod(method);

private MapperMethod cachedMapperMethod(Method method) 
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) 
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    
    return mapperMethod;
  

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) 
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  

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.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方法中对各种返回值类型进行了判断,因此有了下面的SELECT方法个对返回类型的判断从而使用不同SELECT逻辑。

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

就是以上五种情况,第一种返回void,这种还是比较少见的,不做分析。

第二种条件,如果是返回多条数据的,就是返回一个List列表,根据之前的源码分析,executeForMany和selectOne最终调用方法应该是一致的,应该从前文中分析得知selectOne最终还是调用selectList方法,只是对返回结果进行了判断,size大于1时抛出错误。

先转到executeForMany方法,如下:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) 
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) 
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
     else 
      result = sqlSession.<E>selectList(command.getName(), param);
    
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) 
      if (method.getReturnType().isArray()) 
        return convertToArray(result);
       else 
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      
    
    return result;
  

和预想的差不多,调用selectList得到返回结果,但是在返回时对具体返回类型进行了判断,针对返回类型是数组还是集合对查询结果做了不同的包装。对convertToArray和方法convertToDeclaredCollection没必要再去看实现,无非就是List集合转Array或者直接返回之类的。

现在看第二种返回类型,返回类型为Map的,先做个猜测吧,有可能也是调用的selectList方法,然后将返回值放在map中,转到executeForMap方法看看具体吧。

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) 
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) 
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
     else 
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
    
    return result;
  

session调用的是selectMap方法,和预想中还是有一些不同的,有些意思,继续转到selectMap方法中。

@Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) 
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
        configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<V>();
    for (V o : list) 
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    
    return mapResultHandler.getMappedResults();
  

底层还是调用的selectList方法,倒也是没怎么出乎意料之外,基本上其他查询方法都能用selectList包装出来,在查询出结果后,再对结果进行包装,最后返回map,不过这里最开始还是有一个疑惑,就是这里查询出来的list是单条记录的kv的集合还是多条数据库中查询出来的记录,后来我做了个测试,在mapper中加一个返回map的方法,然后再用session调用一下,具体演示源码就不展示了,然后发现如果直接定义返回类型是Map集合的时候仍然调用的是SELECT中的最后一种情况,就是返回单条记录,然后我又把上面的查询map的条件再看了一遍,在MethodSignature方法中,判断当前方法的返回值是否是map类型,还增加了如下判断:

this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;

private String getMapKey(Method method) 
      String mapKey = null;
      if (Map.class.isAssignableFrom(method.getReturnType())) 
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) 
          mapKey = mapKeyAnnotation.value();
        
      
      return mapKey;
    
  

这里加了一个对方法上是否加了MapKey注解的方法判断,如果有MapKey则认为当前值返回Map类型,如果不是则按返回多条或者单条来处理,这里倒是挺有意思,以后找个时间把返回Map和加上MapKey的效果一起写一篇文章出来。

对返回Map类型分析就到此为止了,我们继续看返回类型为指针的。

else if (method.returnsCursor()) 
          result = executeForCursor(sqlSession, args);

要说返回类型为指针,最早见到使用指针移到返回一条条记录的,还是在C语言里面看到过一回,然后后来写python的时候,使用pymysql的时候,其他不多说,转到executeForCursor方法。

private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) 
    Cursor<T> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) 
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
     else 
      result = sqlSession.<T>selectCursor(command.getName(), param);
    
    return result;
  

底层调用的仍然是sqlSession中的selectCursor方法,继续追溯。

@Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) 
    try 
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
     catch (Exception e) 
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     finally 
      ErrorContext.instance().reset();
    
  

@Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException 
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  

@Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException 
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleCursorResultSets(statement);
  

这里挺有意思,最终到StatementHandler还是调用了execute方法,然后对当前statement对象做了一层包装,可以继续看看handleCursorResultSets。

 public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException 
    ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());

    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();

    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    if (resultMapCount != 1) 
      throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
    

    ResultMap resultMap = resultMaps.get(0);
    return new DefaultCursor<E>(this, resultMap, rsw, rowBounds);
  

这个方法做的事情就是继续包装statement对象,获取到statement返回的第一条结果,然后将resultMap、包装对象rsw、rowBounds包装为DefaultCursor对象返回。这里有空以后继续深究一下。

最后一种SELECT其实就没有太多可以分析的,前面文章中几乎都是对selectOne查看的源码。


对Select几种查询的分析就到这了,不过对于MapKey和Cursor这几种以后有空可以继续探索一下,还是挺有意思的。

以上是关于Mybatis源码分析之Select返回数据分析的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis源码阅读之--本地(一级)缓存实现原理分析

MyBatis源码分析select源码分析及小结

Mybatis源码阅读之--整体执行流程

mybatis之mapper.xml分析

精尽MyBatis源码分析 - SQL执行过程之 StatementHandler

mybatis源码分析之06二级缓存