错误描述
apache.ibatis.binding.BindingException: Parameter 'xxx' not found. Available parameters are [argl, arg0, paraml, param2]
问题
- 为什么使用@Param执行形参别名不会出现问题?
- xxxMapper.xml文件中sql语句的#{}、${}使用的变量名 是如何映射 Dao层接口中的方法参数的?(包括使用@Param和不使用@Param的情况)
分析
1. @Param
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
String value();
}
其中value就是@Param(‘XXX’)中指定的XXX,也就是Dao层接口方法的形参的别名。
2. 底层代码
Mybatis执行查询时,xxxMapper.xml中的sql语句 是通过 org.apache.ibatis.reflection.ParamNameResolver
类中的 ParamNameResolver
方法 找到Dao层接口方法中的参数的。
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;
// 遍历方法的所有形参,paramIndex为形参在方法参数中的位置
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
// name是Dao层接口中方法的形参名
String name = null;
// 遍历Dao层接口方法当前形参的注解
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 如果当前形参使用注解,判断注解是否是@Param类型
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 是,就获取@Param指定的形参的别名
// 这样xml中的sql语句 就能在 Dao层接口方法中根据形参别名找到对应的形参
name = ((Param) annotation).value();
break;
}
}
// 如果Dao层接口方法形参没有使用@Param
if (name == null) {
// 那么,判断Mybatis是否允许使用形参的参数名作为索引(下面有解释,重点)
// 也就是是否允许xml文件中sql使用Dao层接口中形参名 查找 对应的形参
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
// 如果不允许使用参数真实名称
if (name == null) {
// 就使用当前参数的位置,作为sql语句查找接口方法的 索引
// use the parameter index as the name ("0", "1", ...)
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
我们当前讨论的前提是:Dao层接口方法多参数形况下不使用 @Param,那么Mybatis就会通过 config.isUseActualParamName()
方法:
public boolean isUseActualParamName() {
return useActualParamName;
}
config是mybatis的配置对象,里面的配置项目可以影响mybatis的行为,具体配置项目可以从mybatis官方文档查询,这里我们就看一下useActualParamName参数的含义:
设置名 | 描述 | 值 |
---|---|---|
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true 或者 false |
允许我们使用mapper接口方法的参数名称当作sql语句的参数名称,而且也不需要@Param注解,这个属性默认是开启的,使用这个特性还有以下几个要求:
①采用 Java 8 编译。
②编译时加上-parameters 选项。
③mybatis在3.4.1以上
-parameters
可以在IDEA的 Settings -> Build -> Comliler -> Java Compiler
中设置:
总结
❗❗❗所以,如果我们在编译时加上 -parameters
,那么就可以使用Dao层接口方法的真实形参名,使得sql语句中的#{}、${}引用的变量名 能够找到 Dao层接口方法中对应的形参。就不会报找不到的错误了。
问题1: 为什么使用@Param执行形参别名不会出现问题?
回答:
// 如果当前形参使用注解,判断注解是否是@Param类型
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 是,就获取@Param指定的形参的别名
// 这样xml中的sql语句 就能在 Dao层接口方法中根据形参别名找到对应的形参
name = ((Param) annotation).value();
break;
}
问题2: xxxMapper.xml文件中sql语句的#{}、${}使用的变量名 是如何映射 Dao层接口中的方法参数的?(包括使用@Param和不使用@Param的情况)
回答:
Mybatis执行查询时,xxxMapper.xml中的sql语句 是通过 org.apache.ibatis.reflection.ParamNameResolver
类中的 ParamNameResolver
方法 找到Dao层接口方法中的参数的。
1、是否使用@Param,是就获取@Param中的形参别名
2、否则,判断是否允许使用 Dao层接口方法真实形参名,是就获取对应形参名
3、否则,就是用形参在接口方法中的参数
例子
验证一下-parameters这个选项的作用:
public static void test(int param1, String param2, Object param3){
// 方法体内容
}
- 不使用
-parameters
:
parameterMethodTest
方法的参数 编译后:arg0、arg1、arg2 - 使用
-parameters
:
parameterMethodTest
方法的参数 编译后:param1、param2、param3
那对于sql:
select * from test where param1 = #{param1} and param2 = #{param2} and param3 = #{param3}
- 如果使用了
-parameters
,在执行的时候就能通过param1
、param2
、param3
找到接口方法中对应的形参。 - 如果没有使用
-parameters
,sql语句在执行时,通过param1 = #{param1} and param2 = #{param2} and param3 = #{param3}
寻找方法对应参数,发现方法中的参数是arg0、arg1、arg2
,那么就会报Parameter 'xxx' not found.
错误。