手写mybatis之解析和使用ResultMap映射参数配置
前言
学习源码是在学习什么呢?
就是为了通过这些源码级复杂模型中,学习系统框架的架构思维、设计原则和设计模式。在这些源码学习手写的过程中,感受、吸收并也是锻炼一种思维习惯,并尝试把这些思路技术迁移到平常的复杂业务设计开发中。
目标
通常在数据库表字段的命名中,所定义的规范是希望使用小写的英文字母和下划线的方式组合使用,例如:雇员表中的雇员姓名,则使用 employee_name 表字段描述。但这样的字段定义与 Java 代码开发中的 PO 数据库对象中的字段,是不能一一匹配的,因为 Java 代码中会使用驼峰的方式进行命名,同样是雇员姓名在 Java 代码中则是 employeeName 的方式进行表示。
那么这里就有一个问题了,我们在使用 Mybatis 框架的时候,如果遇到这样的字段,则需要通过把数据库表中的下划线的字段名称,映射成 Java 代码中的驼峰字段,这样才能在执行查询操作的时候,正确的把数据库中的结果映射到 Java 代码的返回对象上。注意:另外在 Mybatis 中也可以使用例如 employee_name as employeeName 的方式进行处理,但在整个编程中并不是太优雅,因为所有的查询都要做 as 映射,那么使用一个统一的字段映射更加合理。
设计
以 XMLMapperBuilder 解析为入口,扩展 resultMapElements 方法,解析 resultMap 映射参数。解析过程涉及到了 MapperBuilderAssistant 映射器构建助手类的使用,所以需要在 XMLMapperBuilder 构造函数中进行初始化。
参数的解析细节主要在 MapperBuilderAssistant 映射构建器助手中完成,包括解析 javaTypeClass、typeHandlerInstance,以及封装 XML 配置的基本字段映射信息。
解析映射配置
整个配置解析都以围绕在 Mybatis ORM 框架中使用 resultMap 映射为主,而 resultMap 的参数映射配置也是用于解决数据库表中的字段与 Java 代码中的对象字段不一致的情况。
解析入口
基于这样对映射字段的解决方案,所以需要扩展 Mapper XML 映射构建器 configurationElement 方法的处理内容,添加解析 resultMap 操作。这部分的操作也就是在解析整个 select、insert、update、delete 之前就操作这个部分,因为后续解析 select 标签时,如果遇到有 resultMap 的配置参数,则可以直接调用已经解析的 resultMap 参数进行关联。
private void configurationElement(Element element) {// 1.配置namespaceString namespace = element.attributeValue("namespace");if (namespace.equals("")) {throw new RuntimeException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2. 解析resultMapresultMapElements(element.elements("resultMap"));// 3.配置select|insert|update|deletebuildStatementFromContext(element.elements("select"),element.elements("insert"),element.elements("update"),element.elements("delete"));
}
在 XMLMapperBuilder#configurationElement 配置元素解析的方法中,新增加了关于 resultMap 元素的解析,由于可能在一个 Mapper XML 中有多组这样的映射参数配置,所以这里获取的是一个 elements 集合元素。
解析过程
private ResultMap resultMapElement(Element resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {String id = resultMapNode.attributeValue("id");String type = resultMapNode.attributeValue("type");Class<?> typeClass = resolveClass(type);List<ResultMapping> resultMappings = new ArrayList<>();resultMappings.addAll(additionalResultMappings);List<Element> resultChildren = resultMapNode.elements();for (Element resultChild : resultChildren) {List<ResultFlag> flags = new ArrayList<>();if ("id".equals(resultChild.getName())) {flags.add(ResultFlag.ID);}// 构建 ResultMappingresultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));}// 创建结果映射解析器ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, resultMappings);return resultMapResolver.resolve();
}
解析的核心过程包括读取 resultMap 标签中,如: 的 id、type 信息。之后开始循环解析标签内的每一条配置元素 如 中的 colum、property 信息。同时这里会把id的配置专门用 ResultFlag 枚举类进行标记。
基础信息解析完成后,就开始调用结果映射器把解析的信息封装成 ResultMap 进行存放。
存放映射对象
public class ResultMapResolver {private final MapperBuilderAssistant assistant;private String id;private Class<?> type;private List<ResultMapping> resultMappings;// ... 省略构造函数public ResultMap resolve() {return assistant.addResultMap(this.id, this.type, this.resultMappings);}}
ResultMapResolver 结果映射器是本章节新增加的内容,其实它的作用就是对解析结果内容的一个封装处理,最终调用的是还是 MapperBuilderAssistant 映射构建器助手,所提供 ResultMap 封装和保存操作。
封装 ResultMap
public class ResultMap {private String id;private Class<?> type;private List<ResultMapping> resultMappings;private Set<String> mappedColumns;private ResultMap() {}public static class Builder {private ResultMap resultMap = new ResultMap();public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {resultMap.id = id;resultMap.type = type;resultMap.resultMappings = resultMappings;}public ResultMap build() {resultMap.mappedColumns = new HashSet<>();// 添加 mappedColumns 字段for (ResultMapping resultMapping : resultMap.resultMappings) {final String column = resultMapping.getColumn();if (column != null) {resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));}}return resultMap;}}// ... 省略get方法}
ResultMap 中 Builder 建造者负责完成字段的处理,通过把字段统一转换为大写存放到 mappedColumns 映射字段中。并返回 resultMap 对象。其余的信息都可以通过构造函数进行传递。
添加 ResultMap
public class MapperBuilderAssistant extends BaseBuilder {private String currentNamespace;private String resource;// ... 省略部分方法public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);this.resource = resource;}public ResultMapping buildResultMapping(Class<?> resultType,String property,String column,List<ResultFlag> flags) {Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);builder.typeHandler(typeHandlerInstance);builder.flags(flags);return builder.build();}public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {// 补全ID全路径,如:cn.bugstack.mybatis.test.dao.IActivityDao + activityMapid = applyCurrentNamespace(id, false);ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration,id,type,resultMappings);ResultMap resultMap = inlineResultMapBuilder.build();configuration.addResultMap(resultMap);return resultMap;}}
在 MapperBuilderAssistant 映射构建器助手中,本章新增加了2个方法,构建 Mapping 方法 buildResultMapping、添加 ResultMap 方法 addResultMap。
而构建 buildResultMapping 方法就是在最开始 Mapper XML 映射构建器解析 buildResultMappingFromContext 所调用的 XMLMapperBuilder#buildResultMapping 方法,封装映射配置中 column、property 字段。
另外一个是 MapperBuilderAssistant#addResultMap 方法,从 ResultMapResolver 结果映射器调用添加 ResultMap,最终就是把这个配置保存到 Configuration 配置项中。
使用映射对象
从 DefaultSqlSession 调用方法,执行 SQL 后,就是对结果的封装,主要体现在 DefaultResultSetHandler#handleResultSets 结果收集器的操作中。
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);boolean foundValues = false;final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {final String column = propertyMapping.getColumn();if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {// 获取值final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();Object value = typeHandler.getResult(rsw.getResultSet(), column);// 设置值final String property = propertyMapping.getProperty();if (value != NO_VALUE && property != null && value != null) {// 通过反射工具类设置属性值metaObject.setValue(property, value);foundValues = true;}}}return foundValues;
}
从 DefaultResultSetHandler#handleResultSets 方法开始,调用 handleResultSet 方法,创建结果处理器、封装数据和保存结果。
那么在封装数据阶段,则包括了创建对象和封装对象属性,源码参考DefaultResultSetHandler#getRowValue
测试
CREATE TABLE activity (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',activity_id bigint(20) NOT NULL COMMENT '活动ID',activity_name varchar(64) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '活动名称',activity_desc varchar(128) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '活动描述',create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',update_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (id ),UNIQUE KEY unique_activity_id (activity_id )
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='活动配置';
配置数据源
<environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment>
</environments>
通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
在这里 dataSource 可以按需配置成 DRUID、UNPOOLED 和 POOLED 进行测试验证。
配置Mapper加载方式
<mappers><!-- XML 配置 --><mapper resource="mapper/Activity_Mapper.xml"/>
</mappers>
配置Mapper XML 语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bugstack.mybatis.test.dao.IActivityDao"><resultMap id="activityMap" type="cn.bugstack.mybatis.test.po.Activity"><id column="id" property="id"/><result column="activity_id" property="activityId"/><result column="activity_name" property="activityName"/><result column="activity_desc" property="activityDesc"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/></resultMap><select id="queryActivityById" parameterType="java.lang.Long" resultMap="activityMap">SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activityWHERE activity_id = #{activityId}</select></mapper>
映射对象类
public class Activity {/*** 自增ID*/private Long id;/*** 活动ID*/private Long activityId;/*** 活动名称*/private String activityName;/*** 活动描述*/private String activityDesc;/*** 创建人*/private String creator;/*** 创建时间*/private Date createTime;/*** 修改时间*/private Date updateTime;// ... 省略 get/set}
单元测试
@Before
public void init() throws IOException {// 1. 从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));sqlSession = sqlSessionFactory.openSession();
}@Test
public void test_queryActivityById(){// 1. 获取映射器对象IActivityDao dao = sqlSession.getMapper(IActivityDao.class);// 2. 测试验证Activity res = dao.queryActivityById(100001L);logger.info("测试结果:{}", JSON.toJSONString(res));
}
测试结果
总结
结合着整个框架和 ResultMap 提前预留出来的参数解析框架,添加映射类参数的处理操作。在整个解析的过程中,一个 ResultMap 对应多个 ResultMapping 的关系,把每一条映射都存处理成 ResultMapping 信息,都存放到配置项中。前面我们就提到过 Configuration 伴随整个 session 生命周期
所有的解析操作完成以后就是到了接触处理封装中,这里你可以思考怎么把 SQL 执行的结果和对象封装到一起,普通的对象默认按照对象字段即可封装,而带有下划线的属性字段,则需要根据映射的2个字段,下划线对应非下划线的方式,进行匹配处理。最终返回统一的封装对象结果。
好了到这里就结束了手写手写mybatis之解析和使用ResultMap映射参数配置的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;
相关文章:

手写mybatis之解析和使用ResultMap映射参数配置
前言 学习源码是在学习什么呢? 就是为了通过这些源码级复杂模型中,学习系统框架的架构思维、设计原则和设计模式。在这些源码学习手写的过程中,感受、吸收并也是锻炼一种思维习惯,并尝试把这些思路技术迁移到平常的复杂业务设计开…...

LDR6500:低成本一拖二快充线解决方案
随着科技的飞速发展,我们的电子设备日益增多,从智能手机到平板电脑,再到各种可穿戴设备,它们已成为我们日常生活不可或缺的一部分。然而,随之而来的充电问题也日益凸显。为了解决这一难题,Type-C接口一拖二…...

DS线性表之单链表的讲解和实现(2)
文章目录 前言一、链表的概念二、链表的分类三、链表的结构四、前置知识准备五、单链表的模拟实现定义头节点初始化单链表销毁单链表打印单链表申请节点头插数据尾插数据头删数据尾删数据查询数据在pos位置之后插入数据删除pos位置之后的数据 总结 前言 本篇的单链表完全来说是…...

LeetCode 73 Set Matrix Zeroes 题目解析和python代码
题目: Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0’s. You must do it in place. Example 1: Input: matrix [[1,1,1],[1,0,1],[1,1,1]] Output: [[1,0,1],[0,0,0],[1,0,1]] Example 2: Input: matrix …...

鸿蒙--WaterFlow 实现商城首页
目录结构 ├──entry/src/main/ets // 代码区 │ ├──common │ │ ├──constants │ │ │ └──CommonConstants.ets // 公共常量类 │ │ └──utils │ │ └──Logger.ets // 日志打印类 │ ├──entryability │ │ └──EntryAbility.ets // 程序入口…...

QT 中如何保存matlab 能打开的.mat数据矩阵!
Windows 上安装并使用 MATIO 库来保存 MATLAB 格式的 .mat 文件,需要进行以下步骤: 1. 下载并安装 CMake MATIO 使用 CMake 构建项目,因此你需要先安装 CMake。 前往 CMake 官网下载适用于 Windows 的安装程序并安装。 2. 下载 MATIO 库源…...
菱形继承(多继承)
1. 什么是菱形继承 也就是多继承,C独有的特性。 2. 菱形继承有什么问题? (1)存在内存浪费,多存一份父类的父类。 (2)容易造成二义性(不知道修改哪一个基本属性)。 3. 如…...

【功能安全】什么是Aspice?
背景 如何设计开发一个符合功能安全的模块,大多都是按照Aspice的规范去做。所以理解Aspice就很重要。 什么是Aspice 英文全称:Automotive Software Process Improvement Capability dEtermanition ASPICE4.0文档 汽车软件过程改进及能力评定…...

基于SpringBoot的国家基础信息管理功能的设计与实现
目录 前言 一、标准信息参考 1、信息来源 二、后台基础信息的维护管理 1、实体类和Mapper类 2、业务层和控制层设计 3、前端界面实现 三、管理页面效果 1、列表管理界面 2、国家信息调整 四、总结 前言 在之前的博客中,我们基于GeoTools工具实现了全球各个…...

Python酷库之旅-第三方库Pandas(145)
目录 一、用法精讲 656、pandas.Timestamp.resolution属性 656-1、语法 656-2、参数 656-3、功能 656-4、返回值 656-5、说明 656-6、用法 656-6-1、数据准备 656-6-2、代码示例 656-6-3、结果输出 657、pandas.Timestamp.second属性 657-1、语法 657-2、参数 6…...

最懂生活的年轻人,都在喝十元奶茶
文 | 螳螂观察 作者 | 如意 以前的打工人,总把二三十的高价奶茶当成身份的象征,喝上了高价奶茶才能叫做在生活中富养自己。 只是,到盘开支的时候,打工人才猛然发觉,动辄二三十一杯的奶茶,不知不觉刮走了…...

MinIO 学习订阅服务
MinIO 的入门非常简单 — 只需几个简单的命令和一个 100 MB 的小二进制文件,您就可以立即启动并运行一个功能性开发环境。但是,为了在生产规模上利用 MinIO 的全部功能,我们鼓励专业人士更多地了解 MinIO 的广泛功能。我们推出了 MinIO 学习订…...

【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一部分 D3.js 基础知识 第一章 D3.js 简介(已完结) 1.1 何为 D3.js?1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践(上)1.3 数据可…...

用python做一个简单的画板
一,画板的介绍 画板(Paint Board)是一个提供用户绘图、涂鸦和创作的平台。现代数字画板通常是由软件程序实现的,具有多种功能。以下是画板的一些主要特征和功能: 1. 基本绘图工具 画笔和铅笔:用户可以选…...
根据传入的文件流链接实现前端下载
后端传入一个下载的url,实现点击按钮,下载文件。 方式一: 通过window.open(“URL”, _blank) 方式 PS:会打开一个新的页面 import React from react;const DownloadButton () > {// window.open("URL", "_…...

大数据新视界 --大数据大厂之大数据环境下的零信任安全架构:构建可靠防护体系
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于springboot的高校招生系统1拥有两种角色:管理员和用户 管理员:学生管理、专业管理、报名管理、录取通知管理、招生公告管理等 用户:登录注册、报…...
【C++设计模式】行为型模式:观察者模式
文章目录 行为型模式:观察者模式 行为型模式:观察者模式 观察者模式定义了一种一对多的依赖关系:它让一个主题(被观察者)对象关联多个观察者对象,并且当主题对象的状态发生变化时,它会主动通知…...
本篇5K,立志最细,FreeRtos中的信号量Semaphore教程详解!!!
前言:本篇教程,参考韦东山,开发文档,连接放在最后 目录 Semaphore基本概念 二值信号量(Binary Semaphore) 计数信号量(Couting Semaphore) 互斥信号量(Mutex&…...

【Postman】接口测试工具使用
干就完啦 Postman发送get请求案例1: Postman发送post请求案例2 Postman发送其他请求Postman测试实战 学习目标:能够使用Postman发送get/post/put/delete请求并获取响应结果 Postman发送get请求 首先postman是一款接口调试工具,支持win&…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...

【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...