mybatis中@Param用法注意事项

时间:2021-08-06 11:07:42


背景:

   DAO中定义了一个方法为:List<People> getAllPeople(@Param("vo") SearchVo vo,@Param("id")String id) ;

   service层调用这个方法是:  peopleDao.getAllPeople(ViewSearchVo vo,String id) ;

   其中ViewSearchVo 是searchVo 的子类,而且 age字段是ViewSearchVo 特有的属性;   

    但是,但是,我调用传这个方法,发现age这个变量可以正常传达到sql中,让俺一下懵逼了,莫非mybatis使用了类型强制转换,(强制类型转换是有风险的,不提倡使用)


过程:

   我在网上寻找我的疑问,看是否有童鞋遇到我类似的困惑,但是转了几圈没有得到有效的信息(不知道使用什么关键字搜索)。所以就撸其mybaits的源码来看看。

   撸的这个源码是mybatis的3.3.0版本。


   Mapper文件的注册,解析主要靠MapperRegistry这个类,这个就不跳过不分析了。


  第一个关键的类,一个初始化过程中的MapperAnnotationBuilder.parse(),其中parseStatement(method)就是解析入参等信息

Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method); //解析入参信息
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}

  

    

void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method); //返回入参的类型
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

 

private Class<?> getParameterType(Method method) {
Class<?> parameterType = null;
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(parameterTypes[i]) && !ResultHandler.class.isAssignableFrom(parameterTypes[i])) {
if (parameterType == null) {
parameterType = parameterTypes[i];
} else {
// issue #135
parameterType = ParamMap.class; //大于1个的入参,使用map的方式返回
}
}
}
return parameterType;
}


对于只有0或者1入参,getParameterType返回的就是null或者该入参的类型,但是大于一个的入参,返回一个paramMap,而且中paramMap的类型是一个泛型,也就是说,接收的value可以是任何类型。


第二个关键的类是MapperMethod,其中对应的方法是 MapperMethod.MethodSignature.convertArgsToSqlCommandParam(Object[] args),这个方法是将运行中的dao中的入参转化为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 {
    //使用ParamMap<Object>等价于map<String,Object>来保存多个入参
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()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}

总结:paramMap由于value是Object,所以我前面提到的问题,传子类的特定属性也可以通过就可以解释清楚了,mybats的@param接收的参数根本就是object,是在运行时确定它的类型,所以使用@param可以偷懒,直接写成List<People> getAllPeople(@Param("vo") Object vo,@Param("id")Object id),也是正常工作,但是不建议这样操作,会增加代码的阅读困难