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<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
//PS: 循环处理所有参数,第一层for循环参数列表,第二层for循环参数的注解集合
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
如果参数是 RowBounds 类型或 ResultHandler 类型,贝1]跳过对该参数的分析
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<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> 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...