mybatis参数封装Map过程的源码解析
过程是这样的:
在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
<select id="getEmpByIdAndLastName" resultType="com.hfi.model.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
为什么这里写#{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<Integer, String> names;
构造函数中,通过对method的反射,获取Annotations
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 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<String, Object> param = new ParamMap<>(); int i = 0; // 遍历names集合;{0=id, 1=lastName} for (Map.Entry<Integer, String> 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如下:
流程:
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<Integer> ids);
由于只有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}...