MyBatis3源码解析(4)参数解析
简介
上篇文章中探索了查询语句的执行过程,下面我们接着来看看其中的查询参数的解析细节,是如何工作的
参数的解析
在日常的开发中,常见的参数有如下几种:
- 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) {// 没有参数声明并且参数数量为1if (!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);}}}}}
}
从上面大致代码可以看到,在正常情况下,参数的设置都是通过名称取获取的,之间传入或者单个传入的情况比较简单
- 多个参数的情况下,会将所有的参数放入map中,后面根据名称去获取
- 单个参数类的情况下,会调用类的get方法对应获取
那如果混合类型,比如下面的情况:
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生成,生成的时候对应的值从上面得到的参数值获取
MyBatis3源码解析(4)参数解析相关推荐
- Mybatis3 源码解析系列
简介 Mybatis作为一个优秀的Java持久化框架,在我们的日常工作中相信都会用到,本次源码解析系列,就开始探索下Mybatis 总结 在MyBatis的学习中,首先通读了<MyBatis3源 ...
- MyBatis3源码解析(6)TypeHandler使用
简介 在上几篇中,介绍了MyBatis3对参数和结果的解析转换,对于常规数据类型,默认的处理已经足够应付了,但日常开发中会有一些特殊的类型,就可以通过TypeHandler来进行处理 示例准备 本篇文 ...
- MyBatis3源码解析(5)查询结果处理
简介 上篇中解析了MyBatis3中参数是如何传递处理的,本篇接着看看在获取到查询结果后,MyBatis3是如何将SQL查询结果与我们接口函数定义的返回结果对应的 源码 获取结果后处理的入口 在:My ...
- MyBatis3源码解析(3)查询语句执行
简介 上篇探索了MyBatis中如何获取数据库连接,本篇继续探索,来看看MyBatis中如何执行一条查询语句 测试代码 本篇文中用于调试的测试代码请参考:MyBatis3源码解析(1)探索准备 完整的 ...
- MyBatis3源码解析(2)数据库连接
简介 基于上篇的示例感受,下面我们探索下MyBatis连接数据库的细节是如果实现的,在日常使用中是如何能和Druid数据库连接池等配合起来的 源码解析 基于上篇的示例代码: public class ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 从脚本到主类
1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [一] 可以去看原文.这里是补充专栏.请看原文 2. 前言 主要针对yarn-per-job模式进行代码分析. ...
- Android Fragment 从源码的角度去解析(上)
###1.概述 本来想着昨天星期五可以早点休息,今天可以早点起来跑步,可没想到事情那么的多,晚上有人问我主页怎么做到点击才去加载Fragment数据,而不是一进入主页就去加载所有的数据,在这里自己就对 ...
- [darknet源码系列-2] darknet源码中的cfg解析
[darknet源码系列-2] darknet源码中的cfg解析 FesianXu 20201118 at UESTC 前言 笔者在[1]一文中简单介绍了在darknet中常见的数据结构,本文继续上文 ...
- 手机QQ侧滑菜单_从源码上一步步解析效果的实现
本文思想来自洪洋大哥,本来写的原创的,有些朋友看到标题后认为是照搬翔哥的例子,仔细看看,会有不同,不过其中的主要思想还是翔哥的,滑动方面的算法还真是有些区别的,看完了就知道不一样,而且我这人比较啰嗦, ...
最新文章
- 面试:说说你对 ThreadLocal 的认识?
- 联想笔记本不能无线上网
- hibernate和spring学习
- 北斗导航 | NB-IoT——了解什么是NB-IoT,与短报文有什么关系
- 文件服务器 说明,文件服务器搭建说明.pdf
- c语言常用字符串处理函数6,【总结】C语言中常见的字符串处理函数
- iframe 与frameset
- Ubuntu 19.10 Beta 发布,正式版本定于 10 月份
- 戴尔修复已存在12年之久的驱动漏洞,影响数百万个人电脑
- Android点击通知进入详情,Android 点击通知进入正在运行的程序
- PKIX path building failed
- HDU 3622 Bomb Game
- 幸运的袋子(深度优先遍历(Depth First Search,DFS))
- IOS AirPrint功能
- ISP(八) Gamma原理详解
- 使用阿里云服务器三分钟搭建网站
- 笔记本电脑禁用集显会变卡?
- 三菱iQ-R系列PLC控制系统项目全套资料
- 互联网:常见运营术语,PV、UV、GMV、CVR等
- ndows 资源管理器,什么是windows资源管理器