手写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我获取;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/445476.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.MySQL存储过程基础(1/10)

引言 数据库管理系统&#xff08;Database Management System, DBMS&#xff09;是现代信息技术中不可或缺的一部分。它提供了一种系统化的方法来创建、检索、更新和管理数据。DBMS的重要性体现在以下几个方面&#xff1a; 数据组织&#xff1a;DBMS 允许数据以结构化的方式存…

“云计算+高职”:VR虚拟仿真实训室的发展前景

随着科技的飞速进步&#xff0c;云计算与虚拟现实&#xff08;VR&#xff09;技术的结合正在深刻改变着教育领域&#xff0c;尤其是在高等职业教育中&#xff0c;这一融合为实训教学带来了革命性的变革。VR虚拟仿真实训室作为这一变革的前沿阵地&#xff0c;正展现出广阔的发展…

Chromium 如何查找V8 引擎中JavaScript 标准内置对象

JavaScript 标准内置对象 - JavaScript | MDN (mozilla.org) 一、JavaScript 标准内置对象 本章介绍和说明了 JavaScript 中所有的标准内置对象、以及它们的方法和属性。 这里的术语“全局对象”&#xff08;或标准内置对象&#xff09;不应与 global 对象混淆。这里的“全局…

出海快报 | “三消+短剧”手游横空出世,黄油相机“出圈”日本市场,从Q1看日本手游市场趋势和机会

编者按&#xff1a;TopOn出海快报栏目为互联网出海从业者梳理出海热点&#xff0c;供大家了解行业最新发展态势。 1.“三消短剧”横空出世&#xff0c;融合创新手游表现亮眼 随着竞争的加剧&#xff0c;新产品想要突出重围&#xff0c;只能在游戏中加入额外的元素。第一次打开…

Spring Boot项目的创建与使用

1.SpringBoot初识 SpringBoot是什么 介绍 ​ 为了简化 Spring 应用的搭建和开发过程&#xff0c;Pivotal 团队在 Spring 基础上提供了一套全新的开源的框架&#xff0c;它就是 Spring Boot。 Spring Boot是由Pivotal团队提供的全新&#xff0c;其设计目的是用来新应用的初始…

Java JWT:原理、机制及案例示范

一、什么是JWT&#xff1f; 1.1 JWT的基本概念 JWT&#xff08;JSON Web Token&#xff09;是一种用于在各方之间传递JSON格式信息的紧凑、URL安全的令牌&#xff08;Token&#xff09;。JWT的主要作用是验证用户身份或权限。它由三部分组成&#xff1a; Header&#xff08;…

Ubuntu 上安装 MySQL 并且实现远程登录

目录 1. 安装MySQL 2. 安全配置MySQL 3. 配置MySQL远程登录 3.1. 允许远程连接 3.2. 重启MySQL服务 3.3. 为用户分配远程访问权限 进入MySQL后&#xff0c;执行以下命令&#xff1a; 3.4. 创建新用户 3.5. 授予权限 3.6. 刷新权限 3.7. 退出 MySQL 控制台 4. 配置防火…

大数据新视界 --大数据大厂之大数据驱动下的物流供应链优化:实时追踪与智能调配

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

科研绘图系列:R语言蝴蝶图(Butterfly Chart)

文章目录 介绍加载R包数据函数画图系统信息介绍 蝴蝶图(Butterfly Chart),也被称为龙卷风图(Tornado Chart)或双轴图(Dual-Axis Chart),是一种用于展示两组对比数据的图表。这种图表通过在中心轴两侧分别展示两组数据的条形图,形似蝴蝶的翅膀,因此得名。蝴蝶图的特点…

汉语言文学做大数据七年实际工作经验分享普通人快来围观

&#xff08;一&#xff09;没有人带你 社会上&#xff0c;都很现实。就是进了公司&#xff0c;有师傅&#xff0c;师傅也没空带你&#xff0c;最多就是有空的时候帮你解决问题。 无论是做啥工作&#xff0c;都要靠自己努力。努力不会成为笑话&#xff0c;不努力就是笑话。就…

STM32 QSPI接口驱动GD/W25Qxx配置简要

STM32 QSPI接口GD/W25Qxx配置简要 &#x1f4dd;本篇会具体涉及介绍Winbond&#xff08;华邦&#xff09;和GD(兆易创新) NOR flash相关型号指令差异。由于网络上可以搜索到很多相关QSPI相关知识内容&#xff0c;不对QSPI通讯协议做深度解析。 &#x1f516;首先确保所使用的ST…

VScode 自定义代码配色方案

vscode是一款高度自定义配置的编辑器, 我们来看看如何使用它自定义配色吧 首先自定义代码配色是什么呢? 看看我的代码界面 简而言之, 就是给你的代码的不同语义(类名, 函数名, 关键字, 变量)等设置不同的颜色, 使得代码的可读性变强. 其实很多主题已经给出了定制好的配色方案…

闯关leetcode——88. Merge Sorted Array

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/merge-sorted-array/description/ 内容 You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and two integers m and n, representing the number of elements …

为什么人工智能用 Python?

人工智能领域倾向于使用Python&#xff0c;主要归因于Python的多个显著优势&#xff1a; 简洁性与可读性&#xff1a;Python的语法设计简洁明了&#xff0c;代码易于阅读和理解&#xff0c;这对于涉及复杂算法和逻辑的人工智能项目尤为重要。它降低了编程门槛&#xff0c;使得…

Unity3D 单例模式

Unity3D 泛型单例 单例模式 单例模式是一种创建型设计模式&#xff0c;能够保证一个类只有一个实例&#xff0c;提供访问实例的全局节点。 通常会把一些管理类设置成单例&#xff0c;例如 GameManager、UIManager 等&#xff0c;可以很方便地使用这些管理类单例&#xff0c;…

DGX的优势

NVIDIA DGX 的 AI 领导力 文章目录 前言一、概述推动跨行业的 AI 创新二、优势客户体验到哪些好处?1. 利用生成式 AI 释放研究人员的潜力2. 加快现代应用程序的上市时间3. 利用 AI 改善客户体验三、性能性能很重要1. 为世界上最先进的超级计算机提供动力2. 打破世界纪录3. 提高…

Go编译为可执行文件

在window下打包成其他系统可运行的文件 1.在window下打包成window下可执行文件 在项目main.go同级目录下&#xff0c;逐条执行以下命令 set CGO_ENABLED0 set GOOSwindows set GOARCHamd64 go build -o main-windows.exe main.go 2.在window下打包成linux 在项目main.go同级目…

Python从入门到高手6.3节-字符串操作方法

目录 6.3.1 字符串常用操作方法 6.3.2 获取字符串长度 6.3.3 字符串的大小写操作 6.3.4 删除字符串中的指定字符 6.3.5 字符串的子串查找 6.3.6 字符串的子串统计 6.3.7 字符串的子串替换 6.3.8 字符串的拆分函数 6.3.9 字符串的前缀与后缀 6.3.10 你一定要成为高手 …

FLINK内存管理解析,taskmanager、jobmanager

1、在 Flink 中设置内存的方法是配置以下两个选项之一&#xff1a; 1&#xff09;Total Flink memory&#xff1a;taskmanager.memory.flink.sizejobmanager.memory.flink.size 2&#xff09;Total process memory&#xff1a;taskmanager.memory.process.sizejobmanager.mem…

Linux驱动开发(速记版)--设备模型

第八十章 设备模型基本框架-kobject 和 kset 80.1 什么是设备模型 设备模型使Linux内核处理复杂设备更高效。 字符设备驱动适用于简单设备&#xff0c;但对于电源管理和热插拔&#xff0c;不够灵活。 设备模型允许开发人员以高级方式描述硬件及关系&#xff0c;提供API处理设备…