Mybatis源码--MapperMethod源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ONROAD0612/article/details/83546761

1 概述

为了便于对Configuration对象的初始化过程的分析,我们这里首先来看看MapperMethod的源码。MapperMethod的作用就是处理Mapper接口函数的注解、参数和返回值。

2 内部类

2.1 MethodSignature

这个内部类主要用于处理函数的参数、注解和返回值。

(1) hasNamedParams函数
该函数用于判断方法上是否有Param注解。

private boolean hasNamedParams(Method method) {

      //获取参数对应的注解
      final Object[][] paramAnnos = method.getParameterAnnotations();
      for (Object[] paramAnno : paramAnnos) {
        for (Object aParamAnno : paramAnno) {
		
	  //判断参数注解是否为Param
          if (aParamAnno instanceof Param) {
            return true;
          }
        }
      }
      return false;
}

(2) getParams函数

该函数的作用是把函数的参数处理成对应位置和名称的集合。

private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
  final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
  
  //获取方法对应的参数类型
  final Class<?>[] argTypes = method.getParameterTypes();
  
  //遍历参数类型
  for (int i = 0; i < argTypes.length; i++) {
  
    //如果参数类型是RowBounds或者ResultHandler就不处理
    if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !          ResultHandler.class.isAssignableFrom(argTypes[i])) {
        String paramName = String.valueOf(params.size());
	  
	//参数有别名,转换成别名,如果没有别名直接用索引
	if (hasNamedParameters) {
	    paramName = getParamNameFromAnnotation(method, i, paramName);
	}
	    params.put(i, paramName);
	}
    }
    return params;
}

(3) convertArgsToSqlCommandParam函数

该函数的作用是将函数参数转换成sql命令参数。

 public Object convertArgsToSqlCommandParam(Object[] args) {
        final int paramCount = params.size();
        if (args == null || paramCount == 0) {
            return null;
        } else if (!hasNamedParameters && paramCount == 1) {
            return args[params.keySet().iterator().next().intValue()];
        } else {
            final Map<String, Object> param = new ParamMap<Object>();
            int i = 0;

            //遍历参数名称集合,并将参数名称和参数值转换成集合
            for (Map.Entry<Integer, String> entry : params.entrySet()) {

                //将参数名称和参数值存入集合		  
                param.put(entry.getValue(), args[entry.getKey().intValue()]);

                //将参数加上param的前缀别名
                final String genericParamName = "param" + String.valueOf(i + 1);
                if (!param.containsKey(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }

(4) getMapKey函数

该函数是处理函数上的mapKey注解的。

private String getMapKey(Method method) {
        String mapKey = null;

        //检查函数的返回类型是不是Map
        if (Map.class.isAssignableFrom(method.getReturnType())) {
            final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);

            //检测函数是否有注解mapKey
            if (mapKeyAnnotation != null) {
                mapKey = mapKeyAnnotation.value();
            }
        }
        return mapKey;
    }

该函数其实就是检测函数是否是返回map并且是否有mapKey注解,如果有就返回。

(5) getUniqueParamIndex函数

该函数用于检测函数是否有唯一参数类型,当然仅仅用于检测参数类型RowBound和ResultHandler。

private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
        Integer index = null;、

        //获取函数所有参数
        final Class<?>[] argTypes = method.getParameterTypes();
        for (int i = 0; i < argTypes.length; i++) {

            //检测参数类型是否存在  
            if (paramType.isAssignableFrom(argTypes[i])) {
                if (index == null) {

                    //获取参数类型对应索引  
                    index = i;
                } else {
                    throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
                }
            }
        }
        return index;
    }

2.2 SqlCommand

这个内部类主要用于处理SQL命令的名称和类型。

针对SqlCommand我们仅仅来看一下它的构造函数。

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {

        //获取节点key(这里的接口全名+函数名 = 命名空间+id)
        String statementName = mapperInterface.getName() + "." + method.getName();
        MappedStatement ms = null;

        //拥有节点key
        if (configuration.hasStatement(statementName)) {

            //获取节点key对应的MappedStatement对象
            ms = configuration.getMappedStatement(statementName);

        //函数不属于接口,这里主要是为了解决继承问题。	
        } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
            String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
            if (configuration.hasStatement(parentStatementName)) {
                ms = configuration.getMappedStatement(parentStatementName);
            }
        }

        //设置sql名称和类型
        if (ms == null) {
            if (method.getAnnotation(Flush.class) != null) {
                name = null;
                type = SqlCommandType.FLUSH;
            } else {
                throw new BindingException("Invalid bound statement (not found): " + statementName);
            }
        } else {
            name = ms.getId();
            type = ms.getSqlCommandType();
            if (type == SqlCommandType.UNKNOWN) {
                throw new BindingException("Unknown execution method for: " + name);
            }
        }
    }

我们可以看出上面的构造函数的作用就是处理<select|update|delete|insert>节点与接口函数的映射关系,并且确定sql命令的名称和类型。
由此我们知道了要获取到接口函数对应的sql,仅仅需要将接口的名称和函数名称组合起来作为key到Configuration中的mappedStatements属性去获取就行。

3 主要函数

(1) execute函数

该函数的主要作用就是调用SqlSession执行sql语句。

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;

        //根据命令类型执行不同的sql语句
        if (SqlCommandType.INSERT == command.getType()) {

            //将函数参数转换成sql命令参数	  
            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()) {

            //返回为void并且有返回结果处理类	  
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;

                //返回结果是集合或者数组	
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);

                //返回结果是Map	
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);

                //返回结果为一般对象		
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);
            }
        } else if (SqlCommandType.FLUSH == command.getType()) {
            result = sqlSession.flushStatements();
        } 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;
    }

针对SqlSession的具体逻辑我们这里先不分析。
在此我们首先来看一下rowCountResult函数:

private Object rowCountResult(int rowCount) {
        final Object result;
        if (method.returnsVoid()) {
            result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            result = Integer.valueOf(rowCount);
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            result = Long.valueOf(rowCount);
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            result = Boolean.valueOf(rowCount > 0);
        } else {
            throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
        }
        return result;
    }

可以看出该函数的作用就是将执行结果转换成Integer、Long或者Boolean。
接下来我们来看一下针对查询处理的几个函数。

(2) executeWithResultHandler函数

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {

        //获取到查询节点对应的MappedStatement
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());

        //节点如果没有返回类型则直接抛出异常
        if (void.class.equals(ms.getResultMaps().get(0).getType())) {
            throw new BindingException("method " + command.getName()
                    + " needs either a @ResultMap annotation, a @ResultType annotation,"
                    + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        }
        Object param = method.convertArgsToSqlCommandParam(args);

        //参数中含有RowBounds类型
        if (method.hasRowBounds()) {

            //获取RouBounds参数,把参数注入到查询语句中
            RowBounds rowBounds = method.extractRowBounds(args);
            sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
        } else {
            sqlSession.select(command.getName(), param, method.extractResultHandler(args));
        }
    }

(3) 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);
        }
        //如果返回类型不是List,则进行处理
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {

            //如果返回类型是数组则将结果转换成数组	  
            if (method.getReturnType().isArray()) {
                return convertToArray(result);

                //将结果转换成要求的集合类型	
            } else {
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        return result;
    }

从上面我们可以看出,executeForMany函数其实也是依赖于SqlSession,这里主要做了就是针对函数的返回结果不是List的情况进行了相应的处理。

(4) executeForMap函数
该函数的作用是针对返回结果为Map的查询。函数比较简单,这里就不做进一步分析了。

最后我们补充对一个类的说明,MappedStatement维护了一条<select|update|delete|insert>节点的封装。里面封装了参数类型、返回类型、查询超时时间、是否使用缓存、主键生成等信息。

至此完成了对MapperMethod核心内容的分析,接下来将分析MapperProxyFactory和MapperProxy类的源码,欢迎交流。

猜你喜欢

转载自blog.csdn.net/ONROAD0612/article/details/83546761
今日推荐