调用 Mapper 接口中的如下方法:
List<User> findList(User user);
最终会调用org.apache.ibatis.binding.MapperMethod#executeForMany
,其内部会调用org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
来获取 Mapper 方法参数:
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);}...
}
convertArgsToSqlCommandParam 方法会调用org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
:
public Object convertArgsToSqlCommandParam(Object[] args) {return paramNameResolver.getNamedParams(args);
}
paramNameResolver 属性在创建 MethodSignature 时实例化:
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {...this.paramNameResolver = new ParamNameResolver(configuration, method);
}
在 ParamNameResolver 的构造方法中会解析方法参数信息,保存到 names 属性中,names 属性类型为SortedMap<Integer, String>
:
public ParamNameResolver(Configuration config, Method method) {final Class<?>[] paramTypes = method.getParameterTypes();final Annotation[][] paramAnnotations = method.getParameterAnnotations();final SortedMap<Integer, String> map = new TreeMap<Integer, String>();int paramCount = paramAnnotations.length;// get names from @Param annotationsfor (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {if (isSpecialParameter(paramTypes[paramIndex])) {// skip special parameterscontinue;}String name = null;for (Annotation annotation : paramAnnotations[paramIndex]) {if (annotation instanceof Param) {hasParamAnnotation = true;name = ((Param) annotation).value();break;}}if (name == null) {// @Param was not specified.if (config.isUseActualParamName()) {name = getActualParamName(method, paramIndex);}if (name == null) {// use the parameter index as the name ("0", "1", ...)// gcode issue #71name = String.valueOf(map.size());}}map.put(paramIndex, name);}names = Collections.unmodifiableSortedMap(map);
}
方法先获取方法参数的注解(注意是Annotation[][]
,没有注解的参数会对应一个空数组),然后遍历参数。
循环内部,通过isSpecialParameter
方法判断参数是否是特殊参数:
private static boolean isSpecialParameter(Class<?> clazz) {return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
可见特殊参数包括:
- RowBounds
- ResultHandler
然后遍历参数的注解,如果注解是@Param
,则获取注解的 value 属性作为参数名。
如果参数没有被@Param
标注(name == null
)且开启了 useActualParamName 配置,则通过反射获取方法参数名,如果未获取到(name == null
),则使用参数索引(注意是map.size()
,而不是方法参数索引)作为参数名。
以参数索引(这里就是方法参数索引了)为键,以得到的参数名为值,保存到 map 集合中,而且注意 map 的类型为TreeMap<Integer, String>
,数据会按参数索引从小到大排序。
在循环结束后,将 map 转换为SortedMap<Integer, String>
,并赋值给 names 属性。所以最终的 names 如下:
The key is the index and the value is the name of the parameter.
The name is obtained from Param if specified. When Param is not specified, the parameter index is used. Note that this index could be different from the actual index when the method has special parameters (i. e. RowBounds or ResultHandler).
- aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
- aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
- aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
也就是:
names 的 key 为参数在形参列表中的索引,value 为参数名。
参数名来自 Param 注解,如果没有被 Param 标注,则使用参数索引作为参数名。注意是参数索引,而不是方法形参列表索引。
比如 aMethod(int a, RowBounds rb, int b) 得到的 names 为 {{0, "0"}, {2, "1"}}
getNamedParams 方法中会用到 names 属性:
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 @Paramif (!names.containsValue(genericParamName)) {param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}
}
getNamedParams 方法会根据参数数量和参数注解情况,返回一个参数对象。
-
如果没有参数,则返回 null。
-
如果参数数量为 1,且该参数没有被 Param 注解标注,则直接返回参数列表中的唯一参数。
-
否则返回一个 Map 对象(param),其中 key 为参数名,value 为参数值。参数名从 names 属性中获取,即 names 属性的值。此外,会在 param 中添加默认参数名(param1,param2,...)。
所以有时候会看到,可以在 XML 中使用 param1 之类来做占位符,不使用 Param 注解标注参数,通常也还是能正常工作。