1. 简介
当使用 MyBatis 时,如果接口中抽象方法有多个简单类型参数,MyBatis无法识别抽象方法参数的自定义名称,需要使用 @Param
为每个参数命名,以检查用户登录为例
有注解:
1
| User checkLoginByParam(@Param("name") String username, @Param("pwd") String password);
|
无注解:
1
| User checkLogin(String username, String password);
|
2. 使用@Param
测试类代码如下,通过 debug 分析 @Param
在底层如何发挥作用
断点设置为 checkLoginByParam
行
1 2 3 4 5 6 7
| @Test public void testCheckLoginByParam() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.checkLoginByParam("hoyt", "907"); System.out.println(user); }
|
进入 org.apache.ibatis.binding.MapperProxy.invoke
public class MapperProxy<T> implements InvocationHandler, Serializable
- 从类名可以看出底层使用的是动态代理
1 2 3 4 5 6 7
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
|
接下来先调用该类中 cachedInvoker(method)
方法返回 MapperMethodInvoker
,再调用其 invoke(proxy, method, args, this.sqlSession)
此处 command 中的 name 即为 MyBatis Mapper 文件中的 命名空间.方法id
execute
方法相对复杂,会对方法的类型分类讨论
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch (this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); }
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
|
这里也可以看到,传到底层的参数是只有值的,没有保存参数名
执行到 param = this.method.convertArgsToSqlCommandParam(args);
然后该方法继续跳转到 getNamedParams
此处关键点即为 names 成员变量,private final SortedMap<Integer, String> names;
,在构造函数中被赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public ParamNameResolver(Configuration config, Method method) { this.useActualParamName = config.isUseActualParamName(); Class<?>[] paramTypes = method.getParameterTypes(); Annotation[][] paramAnnotations = method.getParameterAnnotations(); SortedMap<Integer, String> map = new TreeMap(); int paramCount = paramAnnotations.length;
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) { if (!isSpecialParameter(paramTypes[paramIndex])) { String name = null; Annotation[] var9 = paramAnnotations[paramIndex]; int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) { Annotation annotation = var9[var11]; if (annotation instanceof Param) { this.hasParamAnnotation = true; name = ((Param)annotation).value(); break; } }
if (name == null) { if (this.useActualParamName) { name = this.getActualParamName(method, paramIndex); }
if (name == null) { name = String.valueOf(map.size()); } }
map.put(paramIndex, name); } }
this.names = Collections.unmodifiableSortedMap(map); }
|
这样就获取到了 @Param
中设置的变量名
回到 getNamedParams
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public Object getNamedParams(Object[] args) { int paramCount = this.names.size(); if (args != null && paramCount != 0) { if (!this.hasParamAnnotation && paramCount == 1) { Object value = args[(Integer)this.names.firstKey()]; return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null); } else { Map<String, Object> param = new MapperMethod.ParamMap(); int i = 0; for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) { Map.Entry<Integer, String> entry = (Map.Entry)var5.next(); param.put(entry.getValue(), args[(Integer)entry.getKey()]); String genericParamName = "param" + (i + 1); if (!this.names.containsValue(genericParamName)) { param.put(genericParamName, args[(Integer)entry.getKey()]); } }
return param; } } else { return null; } }
|
现在的param
3. 不使用@Param
不使用 @Param
只能使用 arg0, arg1… 或者 param1, param2…
param 的情况已经分析过了,现在讨论使用 arg 的情况
1 2 3 4
| <select id="checkLogin" resultType="User"> select * from t_user where username = #{arg0} and password = #{arg1} </select>
|
一样打断点进行分析,大部分和 2 一致,区别在于 ParamNameResolver
的构造函数
1 2 3 4 5 6 7 8 9
| if (name == null) { if (this.useActualParamName) { name = this.getActualParamName(method, paramIndex); }
if (name == null) { name = String.valueOf(map.size()); } }
|
后面就是方法的调用,不详细说明了
最终names为
param为