过程是这样的:
在MyBatisTest中调用interface mapper
Employee employee = mapper.getEmpByIdAndLastName(1, "tom");
在com.hfi.dao.EmployeeMapper#getEmpByIdAndLastName方法中
Employee getEmpByIdAndLastName(@Param("id")Integer id, @Param("lastName")String lastName);
其与mybatis映射文件绑定
EmployeeMapper.xml
为什么这里写#{id} #{lastName}能够获取到呢?
如果我没有使用@Param来指定名称,那么要使用{param1} {param2}的原理是什么?
先说结论:
输入参数(可能在interface中带@Param注解),输出是一个map
通过源码debug方式看一下
MapperProxy implements InvocationHandler
if (Object.class.equals(method.getDeclaringClass()))
如果你的method是Object中的method,我们就直接放行
否则就在后面调用
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
到ParamNameResolver类中
map是如何构造出来的
在其构造函数中将names赋值
private final SortedMap names;
构造函数中,通过对method的反射,获取Annotations
public ParamNameResolver(Configuration config, Method method) {
final Class>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap map = new TreeMap();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
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 #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
得到names为
再调用getNamedParams
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
// 参数为null或没有参数直接返回
if (args == null || paramCount == 0) {
return null;
// 如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
// 多个元素或者有Param标注
} else {
final Map param = new ParamMap<>();
int i = 0;
// 遍历names集合;{0=id, 1=lastName}
for (Map.Entry entry : names.entrySet()) {
// names集合的value作为key; names集合的key又作为取值的参考
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 @Param
// 额外的将每一个参数也保存到map中,使用新的key:param1...paramN
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
得到
这样,通过${id}或${param1},就都可以取到1这个参数值了
其UML如下:
手写实现上述过程,参考:手写实现mybatis参数封装Map过程
流程:
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值:
标注了param注解:注解的值
没有标注:
1.全局配置:useActualParamName(jdk1.8支持):name=参数名
2.name=map.size();相当于当前元素的索引
{0=id, 1=lastName}
总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;之后#{key}就可以取出map中的值;
例如:
public Employee getEmp(@Param("id")Integer id,String lastName);
取值:id==>#{id/param1} lastName==>#{param2}
public Employee getEmp(Integer id,@Param("e")Employee emp);
取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
public Employee getEmpById(List
由于只有1个值,所以其不封装map。那应该怎么写呢?
特别注意:如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(其key就是collection),如果是List,封装的更精确,还可以使用这个key(list)。如果是数组,key(array)
取值:取出第一个id的值: #{list[0]}
类似于list={1,2,3,4,5} map=list({0,1),{1,2}...