我们在使用Mybatis时,Mapper接口的方法一般都是长这样
int updatePassword(@Param("id") Integer id, @Param("oldPwd") String oldPwd, @Param("newPwd") String newPwd);
对应的Mapper.xml 如下
<update id="updatePassword">
update t_user
set password = #{newPwd}
where id = #{id} and password = #{oldPwd}
update>
有时候可能应为某个参数的 @Param 注解没有写,或者因为其他原因导致SQL执行报错
org.apache.ibatis.binding.BindingException: Parameter ‘newPwd’ not found. Available parameters are [0, 1, 2, param3, param1, param2]
下面来看看Mybatis 到底是怎么样解析参数的
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
/**
*
* The key is the index and the value is the name of the parameter.
* The name is obtained from {@link Param} if specified. When {@link 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. {@link RowBounds} or {@link 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"}}
*
*/
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
// 解析参数入参名
// 此方法解析完的样子
// 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"}}
public ParamNameResolver(Configuration config, Method method) {
// method 即当前执行的Mapper中的方法
// 获取方法参数类型的数组
final Class<?>[] paramTypes = method.getParameterTypes();
// [[],[],[]]
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 如果参数是RowBounds或ResultHandler类型,及其子类,将跳过
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
// 查看当前循环的参数是否有@Param 注解修饰
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 如果有@Param 注解修饰,则获取其value值,赋值给name
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 如果name == null, 则看Mybatis 是否 开启了useActualParamName 配置(默认是true)
if (config.isUseActualParamName()) {
//如果开启, 则使用方法签名中的名称作为语句参数名称; 即Mapper 方法的形参名
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// 如果name 还是为空,则使用当前参数在参数列表中的下标作为 name ("0","1","2",...)
name = String.valueOf(map.size());
}
}
// 将当前参数在参数列表中的下标和上面获取到的 name 放入map中,然后开始下一个参数的处理
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
/**
* Returns parameter names referenced by SQL providers.
*/
public String[] getNames() {
return names.values().toArray(new String[0]);
}
// 获取参数值
// 返回的参数可能的样子
// null
// 一个Object对象 (方法只有一个入参,且还没有使用@param来修饰这个参数时)
// 一个map
// {{"id",1},{"oldPwd","123"},{"newPwd","456"},{"param1","1"},{"param2","123"},{"param3","456"}} 每个参数都使用了 @param 修饰,
// {{"1",1},{"2","123"},{"3","456"},{"param1","1"},{"param2","123"},{"param3","456"}}
// 每个参数都没有使用 @param 修饰, 且useActualParamName 设置为false (or jdk <1.8)
public Object getNamedParams(Object[] args) {
// 拿到上面解析生成name,获取其size大小
final int paramCount = names.size();
// 如果方法没有传入参数,则返回null
if (args == null || paramCount == 0) {
return null;
// 如果只传入了1个参数,并且还没有使用@param 注解修饰这个参数
} else if (!hasParamAnnotation && paramCount == 1) {
// 则直接将这个参数返回
return args[names.firstKey()];
} else {
// 否则,先创建一个map来封装 参数名 和 参数值
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历names
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 将names中的value (@param 传入的,或0,1,2这些) 和 对应的实参 放入 map中
param.put(entry.getValue(), args[entry.getKey()]);
// param1, param2
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// 如果有重复的key名,将被跳过,避免覆盖
if (!names.containsValue(genericParamName)) {
// 同时还添加 (param1, param2, ...) 这样的key 和对应的实参, 也一同放入map中
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
如果你的JDK 在1.8 或以上,且Mybatis全局配置文件中useActualParamName 配置为true,(默认是true) , 则可以在入参前不加@param注解修饰. 在SQL中,也能使用#{id},这样的形式绑定参数.
<settings>
<setting name="useActualParamName" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="jdbcTypeForNull" value="NULL"/>
settings>
int updatePassword(Integer id,String oldPwd,String newPwd);
对应的Mapper.xml 如下
<update id="updatePassword">
update t_user
set password = #{newPwd}
where id = #{id} and password = #{oldPwd}
update>