01、前言

在正式介绍 mapper 接口注册源码分析之前, 首先来介绍什么是 mapper, 再其次说明下 mapper 与 sql 关联的两种方式

可能比较多的同学使用的是 xml 的方式, 没有接触过注解定义; 注解定义SQL语句本质上是一种轻量级的配置

02、mapper 接口

2.1 什么是 mapper 接口

mapper 接口是用于执行自定义SQL语句相关的方法

可以在 mapper 接口定义方法上添加SQL方法注解或者使 mapper 接口绑定 xml 文件

2.2 使用注解定义 sql

public interface AutoConstructorMapper {@Select("SELECT * FROM subject WHERE id = #{id}")PrimitiveSubject getSubject(@Param("id") final int id);@Select("SELECT * FROM subject")List<PrimitiveSubject> getSubjects();
}

2.3 使用 .xml 文件定义 sql

和上面接口保持一致, 不同的是, sql 的定义放在 .xml 文件中实现

public interface AutoConstructorMapper {PrimitiveSubject getSubject(@Param("id") final int id);List<PrimitiveSubject> getSubjects();
}

这个时候需要定义 mapper 接口对应的 xml 文件来书写 sql 语句

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper"><select id="getSubject" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">SELECT * FROM subject WHERE id = #{id}</select><select id="getSubjects" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">SELECT * FROM subject</select>
</mapper>

mapper 标签的 namespace 属性一定要填写对应 mapper 文件的相对路径

这里科普两个小知识点, 也是看源码想着多研究研究带出来的知识点

2.4 mapper 小知识点

# 壹: <!DOCTYPE> 是用来做什么的? 删除行不行

DOCTYPE 标签中存放的是 mybatis-3-mapper.dtd, 属于 xml 验证, 你书写的 mybatis 相关的版本的标签、关键字是否正确

不能删除, 删除会报出如下错误。结果是定义xml时必须要填写的

文档根元素 "mapper" 必须匹配 DOCTYPE 根 "null"

# 贰: mapper 接口中能不能定义重载方法?

答案是不行, 因为在注册 mapper 中方法时候, 接口名称会被当成定义唯一标识的一部分, mapper 接口中的方法名必须唯一

不相信的同学可以自己试一下, 编译不会有问题, 运行时报错

Mapped Statements collection already contains value for
org.apache.ibatis.autoconstructor.AutoConstructorMapper.getSubjects

聊了点与本文重点无关的知识点, 接下来聊一聊 mapper 是如何注册, 注册到哪里了

03、mybatis 配置文件定义 mappers

如何发现到存在项目中的 mapper 接口或者 .xml 文件呢?

在 mybatis-config.xml 中配置 mappers 标签, 有以下四种注册扫描方式

<mappers><!-- 配置包路径, 扫描配置在包路径的 mapper 接口 --><package name="org.apache.ibatis.autoconstructor"/><!-- 配置 mapper 的 class 属性, 直接加载对应的 mapper 接口 --><mapper class="org.apache.ibatis.autoconstructor.AutoConstructorMapper" /><!-- 使用 mapper 的 resoutce 属性定义 .xml 在项目中的相对路径 --><mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml" /><!-- 配置 mapper 的 url 属性, 加载 .xml 文件所在的绝对路径 --><mapper url="file:///省略.../src/test/java/org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
</mappers>

3.1 定义解析方式

构建 SqlSessionFactory 时通过 XMLConfigBuilder 解析 mybatis 配置文件

由于分支流程大致思路是一致的, 这里使用配置文件中定义 resource, 接下来源码也会以 resource 的方式进行流程解析

<mappers><mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
</mappers>

04、注册 Mapper

注册 mapper 的过程就是在为后续调用数据库拼装 sql 做的初始化工作

将 .xml 中各标签以及 @Select 等注解定义的相关内容进行添加到 Configuration

这里先来串一下流程, 免得大家被层层环绕的源码绕进去

  1. Configuration 初始化时解析 mybatis-config.xml 的 mappers 标签
  2. 由于 mappers 定义了多种方式注册 mapper 接口或 .xml 文件, 会根据不同方式解析
  3. 如果使用 resource 定义, 会根据 XMLMapperBuilder 解析资源路径对应的 .xml 文件
  4. 解析 .xml 文件中 mapper 标签下对应所有标签
  5. 继而添加 mapper 接口到 Configuration 时包装为 MapperProxyFactory
  6. 这里会解析 mapper 接口上定义的 @Select 等注解, 注册结束

4.1 解析标签 mappers

文章上方定义的配置文件解析位置是位于核心初始化 Configuration 方法中 XMLConfigBuilder.parseConfiguration()

private void parseConfiguration(XNode root) {try {...// 解析 mybatis-config.xml 文件中的 <mappers> 标签mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

mapperElement(root.evalNode("mappers"))

解析 mappers 标签的实质, 就是将 .xml 文件中各种各样的标签经过解析器封装为 Java 中定义的对象结构, 并添加至 Configuration

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 扫描包的形式if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {// 获取 resource、url、class 等属性String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");// resource 不为空, url、mapperClass为空进入此分支流程// 文章是以此方式解析if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);// 根据资源路径获取流InputStream inputStream = Resources.getResourceAsStream(resource);// 构建 XML 解析器XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 解析 mappermapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {...} else if (resource == null && url == null && mapperClass != null) {...} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}

4.2 XMLMapperBuilder

XMLMapperBuilder 职责是解析 mapper 对应的 .xml 文件, 父类是 BaseBuilder, 采用了 Builder 设计模式

xxxBuilder 等构建器的作用是帮助 mybatis 解析配置文件以及构建 mapper 等, 关系图如下

点击进 mapperParser.parse(), 查看具体解析

public void parse() {// 检查资源是否已被加载if (!configuration.isResourceLoaded(resource)) {// 解析 <mapper> 标签下对应的 cache-ref、cache、parameterMap、resultMap...// 并添加到 configuration 对应的属性容器中configurationElement(parser.evalNode("/mapper"));// 标记 mapper 已加载, 单 mapper 只解析一次configuration.addLoadedResource(resource);// 根据 namespace 绑定 mapperbindMapperForNamespace();}// 处理解析失败的节点, 再次解析parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();
}

4.3 解析 xxxmapper.xml

解析 xxxmapper.xml 文件 mapper 标签下所有能够解析的标签, 通过处理包装添加到 configuration 对象

private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}// 设置命名空间到 builderAssistant builderAssistant.setCurrentNamespace(namespace);// 解析 <cache-ref> 节点cacheRefElement(context.evalNode("cache-ref"));// 解析 <cache> 节点, 组装 Cache 对象cacheElement(context.evalNode("cache"));// 解析 <parameterMap> 节点, 组装 ParameterMap 对象parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 解析 <resultMap> 节点, 组装 ResultMap 对象resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析 <sql> 节点, 组装 sqlFragments 属性, 存储 <sql> 语句 sqlElement(context.evalNodes("/mapper/sql"));// 解析 <select|insert|update|delete> 节点// 组装 MappedStatement 对象buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}
}

回归 parse(), 也是较为核心的方法 bindMapperForNamespace(), 根据命名空间注册 mapper 动态代理类

private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 根据 namespace 加载对应的 mapper ClassboundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required// 这一步是防止 namespace 中定义的 Class 不存在, 防止报错}if (boundType != null) {// 检查是否已加载if (!configuration.hasMapper(boundType)) {// 为 spring 预留特有资源路径configuration.addLoadedResource("namespace:" + namespace);// 添加 mapper 到 configurationconfiguration.addMapper(boundType);}}}
}

看到这里也就明白了最初的提问, 如何根据 .xml 找到 mapper的, 根据 namespace

Resources.classForName(namespace)

继续跟进 addMapper() 方法的具体实现

// 注册到 configuration 的 mapperRegistry 类中
public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);
}public <T> void addMapper(Class<T> type) {// 判断是否为接口if (type.isInterface()) {// 判断当前类是否已加载if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 具体动态代理组成和调用单独讲knownMappers.put(type, new MapperProxyFactory<T>(type));// 解析 mapper 接口方法上的注解 @Select...@SelectProvider...MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);// 解析并加入 configuration 指定的属性中parser.parse();// 设置加载完成loadCompleted = true;} finally {// 如果加载过程失败, 则将该 Class 从 knownMappers 移除if (!loadCompleted) {knownMappers.remove(type);}}}
}

如果 .xml 文件中定义了 id 为 selectAll 的 select SQL标签, 然后又在 mapper 的方法上又加了 @Select 注解时, 这时就会报错, 重复添加

到这里会发现 mybatis 的设计思路就是将各样式的配置存放到 Configuration, 之后通过引用统一调用

05、mybatis 学习方式

分享一下我是如何学习 mybatis 框架源码的

最开始从 B站、博客、官网 上看一些讲 mybatis 的架构, 提升自己的宏观认识

先从宏观上去了解 mybatis的好处就是, 确保自己不会陷入到某细节

再然后买了两本细节讲源码实现的, 了解组成 mybatis 框架的各层组件

这一步是为了帮助自己将宏观掌握下降到微观认知, 从细节上去看不同功能如何实现

最后去 github 下载源码细跟每一行代码实现, 打笔记, 写博客进行记录, 确保自己真正掌握这些知识点

每个人可能掌握不同的学习方式, 只有适合自己的才是最好的

注册键值的根路径无效_mybatis mapper 接口注册流程源码解析相关推荐

  1. BroadcastReceiver的跨进程注册、接收流程源码解析

    根据<Activity跨进程启动流程源码探究>我们可以清楚以下几点: 1)Context的通用实现是在ContextIml这个类中 2)Activity的启动过程需要借助ActivityM ...

  2. 使用计算机上级考试系统时显示注册键值hkey-路径无效,计算机组装于维护上级考试题解.doc...

    打开资源管理器→选择目录下的要求文件单击鼠标右键→选择WINZIP和Add to Zip file选项→出现Add对话框→在Add to archive文本框中输入C:\考号\EXAM2\KS1→单击 ...

  3. java apk安装路径_android apk安装过程源码解析

    前言: 前一篇博客分析了一下PackageManagerService是如何解析apk的以及我们如何解析未安装apk中的androidManifest.xml文件.解析完肯定要安装的,索性写一篇关于a ...

  4. mysql遍历 xml文件路径_解决Mapper接口和mapper.xml的文件位置问题

    今天遇到一个问题是mybatis中接口和对应的mapper文件位置不同,而引起的操作也会不同,在网上找了好久最终找到了方法,这里就简单的解析一下: 我们知道在典型的maven工程中,目录结构有:src ...

  5. [转]实现键值对存储(长文)

    实现键值对存储(0):目录 本文由 伯乐在线 - 熊铎 翻译.未经许可,禁止转载! 英文出处:Emmanuel Goossaert (CodeCapsule.com).欢迎加入翻译组. 2014年7月 ...

  6. 执行execute时对象名 retime_record 无效_MyBatis 的执行流程怎么可以讲的这么透彻

    前言 MyBatis可能很多人都一直在用,但是MyBatis的SQL执行流程可能并不是所有人都清楚了,那么既然进来了,通读本文你将收获如下: 1.Mapper接口和映射文件是如何进行绑定的 2.MyB ...

  7. Inno setup 访问注册表键值,获取软件安装路径

    ; 脚本由 Inno Setup 脚本向导 生成! ; 有关创建 Inno Setup 脚本文件的详细资料请查阅帮助文档! [Setup] ; 注: AppId的值为单独标识该应用程序. ; 不要为其 ...

  8. 注册表各键值保存的内容及其对用的作用(功能)(不完善,有补充的评论指出供大家学习参考)

    从Windows 95开始,Microsoft在Windows中引入了注册表(英文为REGISTRY)的概念(实际上原来在Windows NT中已有此概念).注册表是Windows 95及Window ...

  9. windows注册表几大键值

    什么是注册表?  从Windows 95开始,Microsoft在Windows中引入了注册表(英文为REGISTRY)的概念(实际上原来在Windows NT中已有此概念).注册表是Windows ...

  10. 一个wxWidgets判断注册表键值的函数

    项目中需要用wxWidgets对注册表的键值进行读取. 使用wxRegKey的QueryValue方法读取相应键值时,例如以下代码. 如果regdelay键值不存在会抛出一个异常.那么对用户来说体验很 ...

最新文章

  1. 是时候重新定义安全了,阿里云肖力解读安全责任共担模型
  2. 对于计算机系统结构 下列,计算机系统结构模拟试题
  3. java redis缓存理解_Java项目中使用Redis缓存案例
  4. 深入浅出分布式文件系统MogileFS集群
  5. 玩转 Rockchip 的开发板,这些信息你要知道
  6. 删除文件夹下面n天前时间的文件
  7. 实现视频播放器倍速、清晰度切换、m3u8下载功能
  8. Golang中对new和make的理解
  9. Python正则表达式(一看就懂)
  10. android 文件下载
  11. 微信抢票应用个人总结
  12. JDK 19 / Java 19 正式发布
  13. php 密码字符串限制,关于php:密码安全随机字符串函数
  14. R语言ggplot2可视化:使用ggpubr包的ggboxplot函数可视化分组箱图、使用bgcolor函数自定义指定可视化图像的背景色
  15. 继电器开关阿里云IOT上云设置操作
  16. HDU-2224-The shortest path
  17. css爱心代码(抖音上很火)
  18. 2023年日程安排APP哪个好用?日程提醒软件用哪个?
  19. 帆软报表参数面板不显示的解决方案
  20. (Beta)Let's-版本测试报告

热门文章

  1. Atiitt 项目 产品 实现的目标
  2. Atitit.加密算法ati Aes的框架设计v2.2
  3. Atitit 解决Unhandled event loop exception错误的办法
  4. Atitit.iso格式蓝光 BDMV 结构说明
  5. paip.基于urlrewrite的反向代理以及内容改写
  6. paip.提升中文分词准确度---新词识别
  7. paip.oracle 10G 在WIN7安装总结
  8. 路孚特:300天350个版本,旗舰移动产品“0”到“1”的交付之路
  9. Rust : 闭包、move、复制与移动语义
  10. 使用C#的后端Web API:循序渐进教程