系列
- MyBatis拦截器原理介绍
- Mybatis拦截器改写请求参数和结果
- Mybatis 插件兼容动态SQL
- mybatis 参数解析流程附相关案例
- 再谈mybatis执行流程
开篇
- 本篇文章主要基于mybatis-3.3.0版本进行源码分析,目标在于记录mybatis在参数解析过程。
- 了解参数解析过程是为了更好的写mybaits的xml预发以及更好的适应mybatis的参数拦截插件。
- 文章会讲解清楚两个问题点,包括mybatis的执行过程中参数的传递形式和对List的特殊处理。
参数解析过程
- mybatis的参数解析过程主要包含三个步骤,包括:解析方法参数的别名,构建方法参数对象,特殊处理集合对象。
- 解析方法参数的别名:负责解析方法的参数和参数注解,构建参数下标及对应的别名的映射关系。
- 构建方法参数对象:结合参数别名映射关系,构建参数别名和参数对象的映射关系。
- 特殊处理集合对象:针对参数对象为单一集合的场景人工生成list/collection的映射关系。
解析方法参数的别名
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
// mapperInterface会对应的mapper的 interface 接口
public MapperMethod(Class mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public static class MethodSignature {
// 省略其他不强相关的代码
private final SortedMap params;
private final boolean hasNamedParameters;
// 解析方法签名
public MethodSignature(Configuration configuration, Method method) {
// 判断是否有@Param的注解
this.hasNamedParameters = hasNamedParams(method);
// 通过getParams来获取对应的参数值
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
// 解析方法的参数
private SortedMap getParams(Method method, boolean hasNamedParameters) {
final SortedMap params = new TreeMap();
// 1、获取方法的参数类型
final Class[] argTypes = method.getParameterTypes();
// 2、遍历方法的参数类型构建参数下标和别名的映射关系
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
// 默认以参数的下标值作为value值
String paramName = String.valueOf(params.size());
// 如果有注解就遍历该参数对应的注解名字
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
// 返回的params以参数下标作为key,以下标或者别名作为value
return params;
}
// 从@Param注解中获取对应的别名
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
// 遍历方法的所有参数注解,getParameterAnnotations返回的是一个二维数组
// 第一维是参数的下标,第二维是改参数对应的多个注解
final Object[] paramAnnos = method.getParameterAnnotations()[i];
// 如果存在@Param注解就获取注解对应的别名
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
break;
}
}
return paramName;
}
}
- 通过method.getParameterTypes()获取方法的所有参数对象。
- 遍历方法的参数,解析该参数对应的注解@Param对应的别名,如果没有注解用参数下标作为别名。
- 构建方法参数的别名Map对象,其中key为参数顺序,value为别名或参数的下标顺序。
构建方法参数对象
public class MapperMethod {
public static class MethodSignature {
// 解析入参返回mybatis内部处理的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
// 1、参数为空的场景直接返回
if (args == null || paramCount == 0) {
return null;
// 2、没有@Param注解修饰且只有一个参数的场景
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
// 3、其他的场景,包含@Param注解或参数个数大于1
} else {
final Map param = new ParamMap