《从面试题来看源码》,单参数,多参数,如何正确使用 @param
Mybatis Dao 接口中,单参数,多参数,如何正确使用 @Param?
答:单参数、多参数下,都可以用注解或不用注解。单参数,一般不用注解,用了注解 sql 语句参数名必须跟注解名称一致。多参数下,建议使用注解,方便后期调式,如果不用注解必须使用 0,1… 索引 或者 param1,param2…
源码分析
如何初始化,请看该篇文章《从面试题来看源码》,Dao 接口的工作原理
首先还是来看 MapperProxy 代理类调用的时候执行的 invoke 方法
MapperProxy.java
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //如果目标方法继承自Object,则直接调用目标方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { //对jdk7以上版本,动态语言的支持 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //1️⃣从缓存中获取 MapperMethod对象,如果缓存中没有,则创建新的 MapperMethod对象并添加到缓存中 final MapperMethod mapperMethod = cachedMapperMethod(method); //2️⃣调用 MapperMethod.execute ()方法执行 SQL 语 句 return mapperMethod.execute(sqlSession, args); }
首先来看 1️⃣,在 MapperMethod 中封装了 Mapper 接口中对应方法的信息,以及对应 SQL 语句的信息
MapperMethod.java
public MapperMethod(Class mapperInterface, Method method, Configuration config) { //记录了 SQL语句的名称和类型 this.command = new SqlCommand(config, mapperInterface, method); //Mapper 接 口中对应方法的相关信息 this.method = new MethodSignature(config, mapperInterface, method); }
MethodSignature 中使用 ParamNameResolver 处理 Mapper 接口中定义方法的参数列表
ParamNameResolver.java
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 //PS: 循环处理所有参数,第一层for循环参数列表,第二层for循环参数的注解集合 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { 如果参数是 RowBounds 类型或 ResultHandler 类型,则跳过对该参数的分析 if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; //遍历该参数的注解集合 for (Annotation annotation : paramAnnotations[paramIndex]) { //有@param注解 if (annotation instanceof Param) { hasParamAnnotation = true; //获取@Param注解指定的参数名称 name = ((Param) annotation).value(); break; } } //如果没有注解 if (name == null) { // @Param was not specified. // useActualParamName==true时 即name = arg0 ... 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 的结构是这样
如果是使用注解,结构是这样
上面方法的参数列表已经处理完了,下面就要处理参数列表跟传入数值的对应关系了,该过程在开头 2️⃣中进行处理
MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) { Object result; //根据 SQL 语句的类型调用 SqlSession 对应的方法 switch (command.getType()) { case INSERT: { //负责将 args []数组( 用户传入的实 参列表)转换成 SQL 语句对应的参数列表 Object param = method.convertArgsToSqlCommandParam(args); //... break; } case UPDATE: { //... } case DELETE: { //... } case SELECT: //... case FLUSH: //... default: throw new BindingException("Unknown execution method for: " + command.getName()); } //... }
convertArgsToSqlCommandParam () 方法通过 ParamNameResolver 对象的 getNamedParams 实现
ParamNameResolver.java
final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; //未使用@Param且只有一个参数 } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; //处理使用@Param注解指定了参数名称或有多个参数的情况 } else { final Map param = new ParamMap(); int i = 0; for (Map.Entry entry : names.entrySet()) { //将参数名与实参对应关系记录到 param 中 param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) // 下面是为参数创建”param+索号”格式的默认参数名称,例如: param1, param2 等,并添加到param集合中 //PS:所以如果你不用注解的话,SQL中就得用param1,param2... final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param //如果@Param注解指定的参数名称就是”param+索引”格式的,则不需要再添加 if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
最后你会发现 param 中是这个样子
所以说:
多参数下,如果不用注解必须使用 0,1… 索引 或者 param1,param2…
以上就是对该面试题的源码分析。
关注我,给你看更多面试分析