mybatis mapper 接口注册流程源码解析

简介: mybatis mapper 接口注册流程源码解析

在这里插入图片描述

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 mapper
        PUBLIC "-//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());
                    // 解析 mapper
                    mapperParser.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 绑定 mapper
        bindMapperForNamespace();
    }

    // 处理解析失败的节点, 再次解析
    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 Class
            boundType = 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 到 configuration
                configuration.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 下载源码细跟每一行代码实现, 打笔记, 写博客进行记录, 确保自己真正掌握这些知识点

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

相关文章
|
7月前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
SQL 存储 Java
Mybatis源码解析:详述初始化过程
以上就是MyBatis的初始化过程,这个过程主要包括SqlSessionFactory的创建、配置文件的解析和加载、映射文件的加载、SqlSession的创建、SQL的执行和SqlSession的关闭。这个过程涉及到了MyBatis的核心类和接口,包括SqlSessionFactory、SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、Configuration、SqlSession和Executor等。通过这个过程,我们可以看出MyBatis的灵活性和强大性,它可以很好地支持定制化SQL、存储过程以及高级映射,同时也避免了几
249 20
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
域名解析 弹性计算 负载均衡
新手上云教程参考:阿里云服务器租用、域名注册、备案及域名解析流程图文教程
对于想要在阿里云上搭建网站或应用的用户来说,购买阿里云服务器和注册域名,绑定以及备案的流程至关重要。本文将以图文形式为您介绍阿里云服务器购买、域名注册、备案及绑定的全流程,以供参考,帮助用户轻松上手。
|
人工智能 Java 数据库连接
MyBatis Plus 使用 Service 接口进行增删改查
本文介绍了基于 MyBatis-Plus 的数据库操作流程,包括配置、实体类、Service 层及 Mapper 层的创建。通过在 `application.yml` 中配置 SQL 日志打印,确保调试便利。示例中新建了 `UserTableEntity` 实体类映射 `sys_user` 表,并构建了 `UserService` 和 `UserServiceImpl` 处理业务逻辑,同时定义了 `UserTableMapper` 进行数据交互。测试部分展示了查询、插入、删除和更新的操作方法及输出结果,帮助开发者快速上手 MyBatis-Plus 数据持久化框架。
965 0
|
10月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
1572 1
Spring boot 使用mybatis generator 自动生成代码插件
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
1028 0
|
前端开发 Java 数据库连接
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
744 2
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
776 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。

推荐镜像

更多
  • DNS
  • 下一篇
    开通oss服务