MyBatis对JDBC做了很好的封装,其中一个吸引人的地方就是能够对从数据库内查询出来的表的记录集映射生成一系列JavaBean,供应用程序使用。今天跟着源码一层一层探讨一下MyBatis把数据库记录集映射到POJO对象的一个简要的过程。

1. DefaultResultSetHandler类

处理结果集的主要实现类,先从这个方法看

[java] view plain copy
  1. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  2. try {
  3. if(parentMapping != null) {
  4. this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);
  5. } else if(this.resultHandler == null) {
  6. DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
  7. this.<span style="color:#ff0000;">handleRowValues</span>(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);
  8. multipleResults.add(defaultResultHandler.getResultList());
  9. } else {
  10. this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);
  11. }
  12. } finally {
  13. this.closeResultSet(rsw.getResultSet());
  14. }
  15. }

它有这样一些类型的参数:ResultSetMapper、ResultMap、一个List、ResultMapping,上面的的代码主要是用到了这个类自己的方法handleRowValues,并需要一个DefaulResultHandler的对象, handleRowValues方法把处理后的结果列表添加到List<object>内,可以得出一个初步结论:

不管方法handleRowValues里面调用的层次多深,最终把结果集ResultSet经过处理,得到了需要的那些POJO对象并存储到一个List里面。

2. 来看一下方法handleRowValues方法:

[java] view plain copy
  1. public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  2. if(resultMap.hasNestedResultMaps()) {
  3. this.ensureNoRowBounds();
  4. this.checkResultHandler();
  5. this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  6. } else {
  7. this.<span style="color:#ff0000;">handleRowValuesForSimpleResultMap</span>(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  8. }
  9. }

又多了一个RowBounds类型的参数,点进去看一下,封装了limit和offset的一个类,应该是拿来传入limit和offset参数用的,方法中分两种情况分别调用了两个方法,前一种是resultMap中有嵌套,后一种没有嵌套,这里重点看看后一种方法,ResultMap是包装了数据表列和对象属性等信息的一个类。

[java] view plain copy
  1. private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
  2. throws SQLException {
  3. DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
  4. skipRows(rsw.getResultSet(), rowBounds);
  5. while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
  6. ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
  7. Object rowValue =<span style="color:#ff0000;"> getRowValue</span>(rsw, discriminatedResultMap);
  8. storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  9. }
  10. }

又来一个陌生的类型DefaultResultContext,打开看看,并不复杂,这个类封装了结果集个数和当前结果,其中的方法skipRows点过去看看:

[java] view plain copy
  1. private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
  2. if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
  3. if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
  4. rs.absolute(rowBounds.getOffset());
  5. }
  6. } else {
  7. for (int i = 0; i < rowBounds.getOffset(); i++) {
  8. rs.next();
  9. }
  10. }
  11. }

这里不难理解了,它用到了rowBounds参数,根据rowBounds给出的offset移动结果集的起始游标。再回到上一个方法,其中最重要的方法getRowValue:

[java] view plain copy
  1. private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  2. final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  3. Object resultObject = <span style="color:#ff0000;">createResultObject</span>(rsw, resultMap, lazyLoader, null);
  4. if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  5. final MetaObject <span style="color:#ff0000;">metaObject</span> = configuration.newMetaObject(resultObject);
  6. boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
  7. if (shouldApplyAutomaticMappings(resultMap, false)) {
  8. foundValues = <span style="color:#ff0000;">applyAutomaticMappings</span>(rsw, resultMap, metaObject, null) || foundValues;
  9. }
  10. foundValues = <span style="color:#ff0000;">applyPropertyMappings</span>(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
  11. foundValues = lazyLoader.size() > 0 || foundValues;
  12. resultObject = foundValues ? resultObject : null;
  13. return resultObject;
  14. }
  15. return resultObject;
  16. }

3. 注意上述方法的第三行,进一步打开createResultObject方法看看:

[java] view plain copy
  1. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  2. final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
  3. final List<Object> constructorArgs = new ArrayList<Object>();
  4. final Object resultObject = <span style="color:#ff0000;">createResultObject</span>(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  5. if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  6. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  7. for (ResultMapping propertyMapping : propertyMappings) {
  8. // issue gcode #109 && issue #149
  9. if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  10. return <span style="color:#ff0000;">configuration.getProxyFactory().createProxy</span>(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  11. }
  12. }
  13. }
  14. return resultObject;
  15. }

其中调用了它的一个重载方法如下:

[java] view plain copy
  1. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
  2. throws SQLException {
  3. final Class<?> resultType = resultMap.getType();
  4. final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
  5. final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  6. if (hasTypeHandlerForResultObject(rsw, resultType)) {
  7. return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  8. } else if (!constructorMappings.isEmpty()) {
  9. return <span style="color:#ff0000;">createParameterizedResultObject</span>(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
  10. } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
  11. return objectFactory.create(resultType);
  12. } else if (shouldApplyAutomaticMappings(resultMap, false)) {
  13. return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
  14. }
  15. throw new ExecutorException("Do not know how to create an instance of " + resultType);
  16. }

分几种不同情况调用了不同的创建实体类对象的方法,主要看看下面这个:

[java] view plain copy
  1. Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
  2. List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
  3. boolean foundValues = false;
  4. for (ResultMapping constructorMapping : constructorMappings) {
  5. final Class<?> parameterType = constructorMapping.getJavaType();
  6. final String column = constructorMapping.getColumn();
  7. final Object value;
  8. try {
  9. if (constructorMapping.getNestedQueryId() != null) {
  10. value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
  11. } else if (constructorMapping.getNestedResultMapId() != null) {
  12. final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
  13. value = getRowValue(rsw, resultMap);
  14. } else {
  15. final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
  16. value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
  17. }
  18. } catch (ResultMapException e) {
  19. throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
  20. } catch (SQLException e) {
  21. throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
  22. }
  23. constructorArgTypes.add(parameterType);
  24. constructorArgs.add(value);
  25. foundValues = value != null || foundValues;
  26. }
  27. return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  28. }

方法通过column解析了一个value,如果在配置中能找到可以映射的属性,那么最后就创建一个POJO对象。再返回到3处的createResultObject,它间接使用了代理工厂,把上述方法返回的对象传给代理工厂的一个代理对象,又进步不把结果传给上一层。 再回到getRowValue方法,其中的metaObject对象使用了上面返回的结果对象做了进一步封装,getRowValue有两个重要的方法:

[java] view plain copy
  1. private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  2. List<UnMappedColumnAutoMapping> autoMapping = <span style="color:#ff0000;">createAutomaticMappings</span>(rsw, resultMap, metaObject, columnPrefix);
  3. boolean foundValues = false;
  4. if (autoMapping.size() > 0) {
  5. for (UnMappedColumnAutoMapping mapping : autoMapping) {
  6. final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
  7. // issue #377, call setter on nulls
  8. if (value != null || configuration.isCallSettersOnNulls()) {
  9. if (value != null || !mapping.primitive) {
  10. metaObject.setValue(mapping.property, value);
  11. }
  12. foundValues = true;
  13. }
  14. }
  15. }
  16. return foundValues;
  17. }
[java] view plain copy
  1. private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
  2. throws SQLException {
  3. final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  4. boolean foundValues = false;
  5. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  6. for (ResultMapping propertyMapping : propertyMappings) {
  7. String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
  8. if (propertyMapping.getNestedResultMapId() != null) {
  9. // the user added a column attribute to a nested result map, ignore it
  10. column = null;
  11. }
  12. if (propertyMapping.isCompositeResult()
  13. || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
  14. || propertyMapping.getResultSet() != null) {
  15. Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
  16. // issue #541 make property optional
  17. final String property = propertyMapping.getProperty();
  18. // issue #377, call setter on nulls
  19. if (value != DEFERED
  20. && property != null
  21. && (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()))) {
  22. metaObject.setValue(property, value);
  23. }
  24. if (property != null && (value != null || value == DEFERED)) {
  25. foundValues = true;
  26. }
  27. }
  28. }
  29. return foundValues;
  30. }

请注意上述第一个方法,它调用了如下方法:

[java] view plain copy
  1. private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  2. final String mapKey = resultMap.getId() + ":" + columnPrefix;
  3. List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
  4. if (autoMapping == null) {
  5. autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
  6. final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
  7. for (String columnName : unmappedColumnNames) {
  8. String propertyName = columnName;
  9. if (columnPrefix != null && !columnPrefix.isEmpty()) {
  10. // When columnPrefix is specified,
  11. // ignore columns without the prefix.
  12. if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
  13. propertyName = columnName.substring(columnPrefix.length());
  14. } else {
  15. continue;
  16. }
  17. }
  18. final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
  19. if (property != null && metaObject.hasSetter(property)) {
  20. final Class<?> propertyType = metaObject.getSetterType(property);
  21. if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
  22. final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
  23. autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
  24. } else {
  25. configuration.getAutoMappingUnknownColumnBehavior()
  26. .doAction(mappedStatement, columnName, property, propertyType);
  27. }
  28. } else{
  29. configuration.getAutoMappingUnknownColumnBehavior()
  30. .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
  31. }
  32. }
  33. autoMappingsCache.put(mapKey, autoMapping);
  34. }
  35. return autoMapping;
  36. }

在这里可以窥视到从数据表的列如何映射到对象的属性的一点端倪了:

首先把resultMap中取得的列名转换为大写字母,再截取它的前缀(去除特殊字符),把这个前缀和要映射到的对象的属性进行比对,符合的就映射过去,即对POJO对象注入对应属性值。这里应该不受到字母大小写的影响。

当然,鉴于本人的水平是在有限,做的这个解释还非常模糊,读者有兴趣可以自行研究一下源码。

MyBatis查询结果集映射到JavaBean原理浅谈相关推荐

  1. Java 线上问题排查神器 Arthas 快速上手与原理浅谈

    [Arthas 官方社区正在举行征文活动,参加即有奖品拿哦~点击投稿] 作者 | 杨桢栋,笔名叫蛮三刀把刀,是一名一线互联网码农,留美访学一年,主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向 ...

  2. 深度学习 | BN层原理浅谈

    深度学习 | BN层原理浅谈 文章目录 深度学习 | BN层原理浅谈 一. 背景 二. BN层作用 三. 计算原理 四. 注意事项 为什么BN层一般用在线性层和卷积层的后面,而不是放在激活函数后 为什 ...

  3. PS-第十二天-PS色阶的使用原理浅谈

    PS色阶的使用原理浅谈 色阶是什么:色阶就是用直方图描述出的整张图片的明暗信息. 样图 如图,从左至右是从暗到亮的像素分布,黑色三角代表最暗地方(纯黑),白色三角代表最亮地方(纯白).灰色三角代表中间 ...

  4. 汽车钥匙芯片工作原理 浅谈汽车钥匙芯片作用及分类

    工程师谭军 发表于 2018-10-08 10:01:00 http://m.elecfans.com/article/791926.html 本文主要是关于汽车钥匙芯片的相关介绍,并着重对汽车钥匙芯 ...

  5. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  6. TreeMap原理(浅谈)

    文章目录 一. 回顾 二. 储备知识 2.1 Comparable和Comparator 2.2 一致性hash 三. TreeMap 3.1 成员变量 3.2 静态常量 3.3 构造方法 到这里,提 ...

  7. php smarty 原理,php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  8. [深度学习-原理]浅谈Attention Model

    系列文章目录 深度学习NLP(一)之Attention Model; 深度学习NLP(二)之Self-attention, Muti-attention和Transformer; 深度学习NLP(三) ...

  9. springboot前后端分离后权限原理浅谈

    1. 需求描述 最近在梳理springboot前后端分离后的权限管理问题.前段时间,已经把shiro的实现和spring security 的实现进行了初步的了解.如果深入细节,一个篇幅怕是不够.本文 ...

最新文章

  1. 虚拟机网络连接方式linuxcentos
  2. mysql数据库语法_MySQL数据库基本语法
  3. 各类常见的网站检查工具
  4. redis transaction和connection命令操作
  5. Codeforces Round #420 (Div. 2)
  6. 什么是java序列化,如何实现java序列化?
  7. MySQL中清空表和截断表的区别(新手入门.)
  8. 摘来的去视频网站的广告方法
  9. Storm 性能优化
  10. CountDownLatch和CyclicBarrier 举例详解
  11. java 不登录购物车_java-没有用户登录时存储购物车(playframework疑问)
  12. 峰瑞资本李丰:共享经济如何改造专业且非标准化的服务业
  13. 哈勃(Hubble)太空望远镜:人类的大眼睛
  14. Unity与Android通信交互
  15. 前端学习: 用css设置文字样式
  16. 嵌入式开发之NorFlash 和NandFlash
  17. 断网的html页面,断网情况下,前端页面处理
  18. 基于HTML5的捕鱼达人游戏网页版
  19. Analyzing Neural Time Series Data 读书笔记6
  20. 拓嘉辰丰电商:多多国际入驻,需要什么资质条件

热门文章

  1. mysql 插入记录时自动生成8位随机数字
  2. java abstract 继承_java 抽象类abstract的继承与实现
  3. 液压安全阀行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  4. 广东电信 DNS 设置更改
  5. web页面各种颜色大概预览
  6. 关于最近无锡永中Office的消息
  7. 刘晓明大使在《电讯报》的英文原文
  8. SurfaceView的基础使用
  9. 合格CTO需要具备的素质能力有哪些?
  10. java设计模式深度理解分析