美文网首页
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