简介
上篇文章中探索了查询语句的执行过程,下面我们接着来看看其中的查询参数的解析细节,是如何工作的
参数的解析
在日常的开发中,常见的参数有如下几种:
1.直接传入: func(Object param1, Object param2, ...)
2.放入 Map 中进行传入:func(Map<String, Object> param)
3.类传入: func(Object param)
上面的请求是如何对应的呢,下面让我们带着疑问跟着源码走一走
参数解析前的相关准备工作
在上篇中,在 ParamNameResolver 有获取参数列表的代码,大体上从 names 中遍历获取的,这里就涉及到 names 的初始化的相关代码,如下:
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
private final boolean useActualParamName;
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
// 初始化话的时候,将相关的name已初始化好
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);
}
}
复制代码
在上面的代码中,names 在类构造函数中已经生成好了,后面获取值的时候直接用即可
而在 ParamNameResolver 的构造函数中,通过初步跟踪代码,是直接读取的接口函数参数获取得到的参数,也就是在情况 3 中传入类,是当做一个参数,后面这个类会一直传递下去
ParamNameResolver 在 MapperMethod 中就已经初始化好了
public class MapperProxy<T> implements InvocationHandler, Serializable {
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
if (m.isDefault()) {
......
} else {
return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
}
public class MapperMethod {
public static class MethodSignature {
......
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
......
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
}
复制代码
跟踪代码下来,首先进行相关的初始化工作,而后在进行参数的解析获取
参数值初步获取
初始化完成后,在代码语句执行前,会获取参数值列表,下面是具体的处理逻辑:
public class ParamNameResolver {
public Object getNamedParams(Object[] args) {
// 获取参数的数量
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
// 没有参数声明并且参数数量为1
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 ParamMap();
int i = 0;
// 遍历name获得参数列表
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
Entry<Integer, String> entry = (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;
}
}
}
复制代码
在上面处理中,如果参数列表中唯一只有一个类参数,那这个参数也算是一个参数,会直接返回类,比如在示例中会直接返回:Person(id=1,name=1),后面获取参数值填充时,会使用类 get 方法获取值,这个在下面会接着分析
而且注意在 param 中,会存入两个东西,一个 argx,一个是 paramx,感觉是和 ${}和 #{}有关,这个后面再分析,param 在示例中会如下:
#如果不加@Param注解
param:
- arg0: 1
- arg1: 1
- param0: 1
- param1: 1
#如果加@Param注解
param:
- id: 1
- name: 1
- param0: 1
- param1: 1
复制代码
在这里就得到后面要用的参数了,这里需要注意了,如果是单个参数,那就是直接返回对应的值;如果是多个参数,那就会放到一个 map 中,这个 map 中的 key 是非常关键的,因为构造 preStatement 是根据名称从里面取值的,后面会有相关代码
PreStatement 的生成
咋上面得到参数后,并不是直接使用,而在在 PreStatement 生成的时候用于传入的,关键的代码如下:
public class SimpleExecutor extends BaseExecutor {
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
// 这里会进行参数的传入
handler.parameterize(stmt);
return stmt;
}
}
public class DefaultParameterHandler implements ParameterHandler {
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
// 上面的三个先不管,下面就是获取参数的具体的逻辑
// 如果是类,会通过一些处理调用对应的get方法
// 如果多个之间传递的参数,在上面会放入一个map,之间从map中获取即可
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}
// 和参数拦截器相关的,后面再解析,先放过
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}
try {
// 设置相关的值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
}
}
}
}
}
}
复制代码
从上面大致代码可以看到,在正常情况下,参数的设置都是通过名称取获取的,之间传入或者单个传入的情况比较简单
那如果混合类型,比如下面的情况:
public interface PersonMapper {
@Select("Select id, name from Person where id=#{id} and name=#{person.name}")
@Results(value = {
@Result(property = "id", column = "id"),
@Result(property="name", column = "name"),
})
Person getPersonByMul(@Param("person") Person person, @Param("id") Integer id);
}
复制代码
我们一直根据下,从下面的代码中得到,如果是类,会递归去获取
public class MetaObject {
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());
// 如果是类,后面会再次调用getVale获取
return metaValue == SystemMetaObject.NULL_META_OBJECT ? null : metaValue.getValue(prop.getChildren());
} else {
return this.objectWrapper.get(prop);
}
}
}
复制代码
总结
通过本篇的探索,我们大致了解了 MyBatis3 的参数获取解析原理
1.在初始化阶段,获取接口函数的参数列表,初始化 names,用于后面获取参数值
2.获取参数值,分单个或者多个参数的情况,而且根据参数的类型,有不同的处理方法
单个参数:直接返回
多个参数:放入 Map 中返回,后面根据 key 进行获取
参数类型是类:后面调用类的 get 防护获取对应的值
参数类型是 Map:后面直接调用 get 方法
3.PreStatement 生成,生成的时候对应的值从上面得到的参数值获取
评论