简介

上篇文章中探索了查询语句的执行过程,下面我们接着来看看其中的查询参数的解析细节,是如何工作的

参数的解析

在日常的开发中,常见的参数有如下几种:

  • 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)参数解析相关推荐

  1. Mybatis3 源码解析系列

    简介 Mybatis作为一个优秀的Java持久化框架,在我们的日常工作中相信都会用到,本次源码解析系列,就开始探索下Mybatis 总结 在MyBatis的学习中,首先通读了<MyBatis3源 ...

  2. MyBatis3源码解析(6)TypeHandler使用

    简介 在上几篇中,介绍了MyBatis3对参数和结果的解析转换,对于常规数据类型,默认的处理已经足够应付了,但日常开发中会有一些特殊的类型,就可以通过TypeHandler来进行处理 示例准备 本篇文 ...

  3. MyBatis3源码解析(5)查询结果处理

    简介 上篇中解析了MyBatis3中参数是如何传递处理的,本篇接着看看在获取到查询结果后,MyBatis3是如何将SQL查询结果与我们接口函数定义的返回结果对应的 源码 获取结果后处理的入口 在:My ...

  4. MyBatis3源码解析(3)查询语句执行

    简介 上篇探索了MyBatis中如何获取数据库连接,本篇继续探索,来看看MyBatis中如何执行一条查询语句 测试代码 本篇文中用于调试的测试代码请参考:MyBatis3源码解析(1)探索准备 完整的 ...

  5. MyBatis3源码解析(2)数据库连接

    简介 基于上篇的示例感受,下面我们探索下MyBatis连接数据库的细节是如果实现的,在日常使用中是如何能和Druid数据库连接池等配合起来的 源码解析 基于上篇的示例代码: public class ...

  6. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  7. 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 从脚本到主类

    1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [一] 可以去看原文.这里是补充专栏.请看原文 2. 前言 主要针对yarn-per-job模式进行代码分析. ...

  8. Android Fragment 从源码的角度去解析(上)

    ###1.概述 本来想着昨天星期五可以早点休息,今天可以早点起来跑步,可没想到事情那么的多,晚上有人问我主页怎么做到点击才去加载Fragment数据,而不是一进入主页就去加载所有的数据,在这里自己就对 ...

  9. [darknet源码系列-2] darknet源码中的cfg解析

    [darknet源码系列-2] darknet源码中的cfg解析 FesianXu 20201118 at UESTC 前言 笔者在[1]一文中简单介绍了在darknet中常见的数据结构,本文继续上文 ...

  10. 手机QQ侧滑菜单_从源码上一步步解析效果的实现

    本文思想来自洪洋大哥,本来写的原创的,有些朋友看到标题后认为是照搬翔哥的例子,仔细看看,会有不同,不过其中的主要思想还是翔哥的,滑动方面的算法还真是有些区别的,看完了就知道不一样,而且我这人比较啰嗦, ...

最新文章

  1. 面试:说说你对 ThreadLocal 的认识?
  2. 联想笔记本不能无线上网
  3. hibernate和spring学习
  4. 北斗导航 | NB-IoT——了解什么是NB-IoT,与短报文有什么关系
  5. 文件服务器 说明,文件服务器搭建说明.pdf
  6. c语言常用字符串处理函数6,【总结】C语言中常见的字符串处理函数
  7. iframe 与frameset
  8. Ubuntu 19.10 Beta 发布,正式版本定于 10 月份
  9. 戴尔修复已存在12年之久的驱动漏洞,影响数百万个人电脑
  10. Android点击通知进入详情,Android 点击通知进入正在运行的程序
  11. PKIX path building failed
  12. HDU 3622 Bomb Game
  13. 幸运的袋子(深度优先遍历(Depth First Search,DFS))
  14. IOS AirPrint功能
  15. ISP(八) Gamma原理详解
  16. 使用阿里云服务器三分钟搭建网站
  17. 笔记本电脑禁用集显会变卡?
  18. 三菱iQ-R系列PLC控制系统项目全套资料
  19. 互联网:常见运营术语,PV、UV、GMV、CVR等
  20. ndows 资源管理器,什么是windows资源管理器

热门文章

  1. Qt学习之路3---Qt中的坐标系统
  2. xcode_8正式版安装遇到的小问题
  3. db2 常用命令(一)
  4. 类别动态绑定到TreeView控件
  5. SpringMVC中的拦截器、过滤器的区别、处理异常
  6. 【实践】数据科学在搜索、广告、推荐系统的应用实践.pdf(附下载链接)
  7. 深度好文!男性在青年时期应为自己做哪些投资?
  8. 报名截止仅剩5天!50万冠军大奖,错过再等一年!
  9. 基于检索的自然语言处理模型研究梳理
  10. 跨境电商独立站模式解析与机会分析?