简介
上篇文章中探索了查询语句的执行过程,下面我们接着来看看其中的查询参数的解析细节,是如何工作的
参数的解析
在日常的开发中,常见的参数有如下几种:
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 生成,生成的时候对应的值从上面得到的参数值获取
评论