【Mybatis源码分析 8】mybatis的mapper接口中dao方法并没有具体实现类,是怎么通过mapper动态代理运行的中讲到mybatis使用mapper接口方法,通过MapperProxy动态代理去执行MapperMethod的execute方法,从而执行CRUD操作。
在MapperMethod对象创建的时候,初始化它的MethodSignature子类的实例成员时,会实例化一个ParamNameResolver参数名解析器,作用时将接口方法的注解描述的sql语句中的参数名解析为接口方法入参名(尤其是对@Param注解的映射)
//MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
// 初始化方法签名
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//MapperMethod.MethodSignature
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class) {
this.returnType = (Class)resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = Void.TYPE.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = this.getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
// 初始化参数解析器
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
在初始化ParamNameResolver时,看一下它的构造函数做了什么
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
private final SortedMap<Integer, String> names; // 保存sql语句中的参数(key为参数在sql语句中的顺序,String为参数名)
private boolean hasParamAnnotation; //标识是否使用了@Param注解
//构造方法
public ParamNameResolver(Configuration config, Method method) {
// 获取接口方法的所有入参类型
Class<?>[] paramTypes = method.getParameterTypes();
// 获取接口方法中的所有@Param注解
Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 创建map保存解析@Param后的参数顺序-参数名
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) {
// 注解为 Param 类型
this.hasParamAnnotation = true;
// 获取@Param注解描述的入参名
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
if (config.isUseActualParamName()) {
name = this.getActualParamName(method, paramIndex);
}
if (name == null) {
name = String.valueOf(map.size());
}
}
// 将sql中使用@Param起的参数别名转换为接口方法入参的参数名并存储到map中
map.put(paramIndex, name);
}
}
//解析完所有的sql参数,到这里就将@Param注解的参数名都转换成接口方法入参名了,然后存储到ParamNameResolver的namas字段中。
this.names = Collections.unmodifiableSortedMap(map);
}
// 其他方法
}
mybatis的mapper接口方法的动态代理执行过程,在MapperMethod执行execute方法,调用SqlSession的select等方法前都会先执行:
Object param = this.method.convertArgsToSqlCommandParam(args);
这一步是去解析sql语句中的参数转换为接口方法入参(调用方法签名的paramNameResolver的getNameParams()方法)
public Object convertArgsToSqlCommandParam(Object[] args) {
return this.paramNameResolver.getNamedParams(args);
}
//paramNameResolver
public Object getNamedParams(Object[] args) {
// 获取参数个数, ParamNameReslover的成员private final SortedMap<Integer, String> names中保存sql语句中的所有参数名(按顺序存储到sortmap<integer, String>, key为String参数名在sql语句中的顺序)
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
if (!this.hasParamAnnotation && paramCount == 1) {
// 如果没有使用@Param注解给参数起别名,并且只有一个参数的时候,直接返回args第一个元素(事实上args也应该只传入一个参数)
return args[(Integer)this.names.firstKey()];
} else {
// 使用了@Param注解或者参数个数大于1
Map<String, Object> param = new ParamMap();
int i = 0;
// 遍历sql语句中的所有参数
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)var5.next();
// 解析sql语句中的参数,映射到传入参数(args)
param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
// 可以使用"param1、param2、param3...paramN"的方式映射入参
String genericParamName = "param" + (i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}