美文网首页
Mybatis系列之四 MappMethod(揭示Mapper类

Mybatis系列之四 MappMethod(揭示Mapper类

作者: 超人也害羞 | 来源:发表于2019-07-17 23:45 被阅读0次

一、思路

  1. 分析MapperMethod的初始化过程,以及其下的成员变量。
  2. 分析MapperMethod的命令的执行过程。

二、MapperMethod的初始化过程的分析

我们在Mybatis系列之MapperProxy(揭示Mapper类背后的执行逻辑)一文中已经可以了解到MapperProxy是怎么处理mapper接口的方法请求的,其关键方法正是private MapperMethod cachedMapperMethod(Method method)。在这个方法中实例化了MapperMethod对象,在MapperMethod中实现了用户的请求的处理逻辑。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

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

那么我们现在就从MapperMethod的构造方法开始分析。
先观察一下MapperMethod的源代码,它的成员变量一共俩,一个是SqlCommand命令类型,还有一个是MethodSignature方法签名。

public class MapperMethod {
  private final SqlCommand command;
  private final MethodSignature method;
  // ... 省略部分代码
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }
  // ... 省略部分代码
}

我们再看看new SqlCommand(config, mapperInterface, method);具体做了什么。
其实在new SqlCommand中只做了一件事,就是在Configuration的mappedStatements中找到对应的方法(mappedStatements是从mapper.xml中解析出来的方法,即平常我们定义的sql)。如果找到了,也就知道了这个方法对应的所要执行的sql的具体细节了。如果没有找到,那么说明对应mapper和xml的对应关系存在了问题,那么就是时候改bug咯。

  public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): " + statementName);
      }
      name = ms.getId();
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }

讲完SqlCommand,我们来看看在this.method = new MethodSignature(config, method);中都做了啥。
其实看类型MethodSignature的类名就大致能猜测出来,它就是封装了一些method相关的信息,主要包括两方面,一个是返回类型,第二个是参数类型。

 public MethodSignature(Configuration configuration, Method method) throws BindingException {
      this.returnType = method.getReturnType();      // 返回值类型
      this.returnsVoid = void.class.equals(this.returnType);      // 返回值是否为void
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());      // 是否返回多个结果
      this.mapKey = getMapKey(method);      // method是否使用了mapKey注解
      this.returnsMap = (this.mapKey != null);       // 返回值是否是Map类型
      this.hasNamedParameters = hasNamedParams(method); // 参数是否自定义了别名
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 是否使用mybatis的物理分页
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 是否有resutlHandler
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters)); // 请求参数解析
    }

三、MapperMethod的命令执行过程的分析

讲完了MapperMethod的初始化过程,我们就来看看MapperMethod是怎么执行命令的。
主要就是分析MapperMethod下的execute方法了。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }

大家都知道DML基本上分为增删改查,我们这里增删改作为一类来分析,单独作为一类来分析。
a. 先看看execute中的增删改,它们很相似。首先都是调用了public Object convertArgsToSqlCommandParam(Object[] args)方法解析用户传递的参数,然后就是调用SqlSession对用的增删改方法。
b. 我们再来关注,可以看到根据返回类型的不同,会执行不同的查询操作。

      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
  1. 我们先看看返回类型是void并且有ResultHandler的方法。它是调用 executeWithResultHandler(sqlSession, args);方法来处理请求。从下面的实现代码中看到其实最终还是调用了SqlSession的包含ResultHandler的select方法。
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    // ... 省略部分内容
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) { // 分页信息
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }
  1. 当返回参数是一个List时,它其实也是调用了SqlSession的selectList方法,然后最后根据method上定义的返回类型进行数据封装。
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
    // 对各种类型的list兼容,包括数组和Collections
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
  1. 当返回类型是一个Map时,也只是调用了SqlSession的selectMap方法。
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;
  }
  1. 最后默认情况下,调用SqlSession的selectOne方法只返回一个对象。
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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }

四、小小的总结

MapperMethod执行命令的本质上就是三步,一是处理请求参数,二是调用SqlSession执行真正的sql,三是根据方法的返回类型封装结果。

ok,今天就先到这儿了,good night ~~~

相关文章

网友评论

      本文标题:Mybatis系列之四 MappMethod(揭示Mapper类

      本文链接:https://www.haomeiwen.com/subject/izehlctx.html