2023-09-11
原文作者:一直不懂 原文地址: https://blog.csdn.net/shenchaohao12321/article/details/79996591

当我们通过DefaultSqlSession的Mapper方式操作数据库时使用如下api:

    <T> T getMapper(Class<T> type);

此方法返回一个实现了type接口的实现类的实力,我们分析一下此实力的创建过程。

    public <T> T getMapper(Class<T> type) {
      return configuration.<T>getMapper(type, this);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
      }
      try {
        return mapperProxyFactory.newInstance(sqlSession);
      } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
      }
    }

可以看到getMapper方法实际是调用MapperRegistry的getMapper方法,MapperRegistry我们在前面已经分析过,它保存了Mapper接口与MapperProxyFactory的映射。通过Mapper接口取得对应的MapperProxyFactory,此类的目的是为了创建 Mapper接口的代理对象MapperProxy,对代理对象的调用委托给了MapperMethod对象的execute方法,具体过程在我前面分析过的一篇文章《Mapper映射的解析过程》里,这里不再叙述。 我们详细分析一下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, mapperInterface, method);
    }

通过MapperMethod的构造方法传入了Mapper的接口类,所有执行的Mapper方法和Mybatis配置中心。构造方法中会新创建两个对象SqlCommand和MethodSignature。下面分析一下这两个对象具体是什么东西。

SqlCommand: 这个类会保存两个成员变量,一个是MappedStatement的id属性,一个是所代表执行sql的类型。

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      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 {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

内部有一个resolveMappedStatement方法,此方法返回在解析xml映射文件保存在Configuration中的MappedStatement,将其id属性保存赋值给name成员变量。将其sqlCommandType赋值给type成员变量。

MethodSignature: 包含了关于执行的Mapper方法的参数类型和返回类型。

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

其中包括方法的返回类型,返回类型是否为void,返回类型是否为Collection或数组或游标,如果参数中含有RowBounds类型保存其在参数列表的索引值,ResultHandler同理,最后还new了一个ParamNameResolver对象,MethodSignature通过此对象可以对外提供获取参数索引值或参数名的方法,因为此对象内部缓存了方法参数列表中除了RowBounds和ResultHandler类型参数的索引值与参数名的键值对。举个列子:

    <li>aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}</li>
    <li>aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}</li>
    <li>aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}</li>

其中参数名由@Param注解指定若没有此注解会通过java8的Parameter的获取真实参数名,前提Mapper类是使用java8编译的并且开启了--parameters编译选项。否则参数名为arg0,arg1...的形式了。

接下来着重讲一下execute方法。

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {
        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;
        }
        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);
          }
          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;
    }

可以看到execute的执行分为增删改查和刷新,除了FLUSH类型操作,都会调用MethodSignature的convertArgsToSqlCommandParam方法将Object[] args的参数类型转换为null,或者单个参数对象或ParamMap三种类型中的一种。

    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
    public Object getNamedParams(Object[] args) {
      final int paramCount = names.size();
      if (args == null || paramCount == 0) {
        return null;
      } else if (!hasParamAnnotation && paramCount == 1) {
        return args[names.firstKey()];
      } else {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) {
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param
          if (!names.containsValue(genericParamName)) {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

如果参数数组中只有一个参数且没有标记@Param则将参数转换为args[0],否则将参数数组转换为一个map其中,key为参数名(也可能是arg0,arg1) value为原始参数对象,并且还为多提供一套k如param1,param2的键值对组合。下面我们以SELECT类型详细分析一下execute方法。

SELECT类型一个有5个分支,我们分析最常用的三种:

1.返回类型为Collection或数组

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

可以看到这个方法最终调用了sqlSession的selectList方法。然后在将结果转换为数组或相应的Collection。

2.返回类型是Map

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

可以看到是调用了sqlSession的selectMap,而selectMap内部其实也是调用了它的selectList方法。

3.返回类型为一个普通对象

    public <T> T selectOne(String statement, Object parameter) {
      // Popular vote was to return null on 0 results and throw exception on too many.
      List<T> list = this.<T>selectList(statement, parameter);
      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;
      }
    }

所以这三种返回类型的查询操作核心就是SqlSession的selectList方法,MapperMethod将SqlCommand的那么属性,通过MethodSignature转换后的参数对象或ParamMap和RowBounds对象(可为空)传入SqlSession的selectList方法执行后再将结果转换为MethodSignature的returnType指定的类型。

下一篇分析DefaultSqlSession的selectList方法。

阅读全文