MyBatis通过注解配置执行SQL语句原理源码分析

news2024/12/21 12:11:20

文章目录

      • 前置准备
      • 流程简要分析
      • 配置文件解析
      • 加载 Mapper 接口
      • MapperAnnotationBuilder解析接口方法注解
      • parseStatement 方法详解
      • MapperBuilderAssistant

前置准备

创建一个mybatis-config.xml文件,配置mapper接口

<mappers>
        <!--注解配置-->
        <mapper class="cn.bugstack.mybatis.test.dao.IUserDao"/>
    </mappers>

创建一个mapper接口,使用注解方式编写SQL

public interface IUserDao {

    @Select("SELECT id, userId, userName, userHead\n" +
            "FROM user\n" +
            "where id = #{id}")
    User queryUserInfoById(Long id);

    @Select("SELECT id, userId, userName, userHead\n" +
            "        FROM user\n" +
            "        where id = #{id}")
    User queryUserInfo(User req);

    @Select("SELECT id, userId, userName, userHead\n" +
            "FROM user")
    List<User> queryUserInfoList();

    @Update("UPDATE user\n" +
            "SET userName = #{userName}\n" +
            "WHERE id = #{id}")
    int updateUserInfo(User req);

    @Insert("INSERT INTO user\n" +
            "(userId, userName, userHead, createTime, updateTime)\n" +
            "VALUES (#{userId}, #{userName}, #{userHead}, now(), now())")
    void insertUserInfo(User req);

    @Insert("DELETE FROM user WHERE userId = #{userId}")
    int deleteUserInfoByUserId(String userId);

}

流程简要分析

【1】 解析 mybatis-config.xml 配置文件
MyBatis 启动时加载 mybatis-config.xml 配置文件,初始化全局配置。
【2】通过配置的mapper路径反射 加载 Mapper 接口
MapperRegistry 注册 Mapper 接口类,检查每个 Mapper 是否已注册。
【3】 创建 MapperProxyFactory
为每个 Mapper 接口创建 MapperProxyFactory,用于生成接口的代理对象。
【4】 解析接口方法注解
MapperAnnotationBuilder 解析接口方法上的 SQL 注解(如 @Select、@Insert 等)。
【5】 注册 SQL 映射
通过 MappedStatement 将 SQL 语句与方法绑定,并注册到 Configuration 中。
【6】 生成代理对象
MapperProxyFactory 使用代理模式为 Mapper 接口生成代理对象。
【7】 执行 SQL 语句
代理对象拦截方法调用,解析注解中的 SQL,交由 MyBatis 执行。

配置文件解析

    /**
     * 解析配置;类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     *
     * @return Configuration
     */
    public Configuration parse() {
        try {
            // 环境
            environmentsElement(root.element("environments"));
            // 解析映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
        return configuration;
    }

mapperElement(root.element(“mappers”));代码解析出配置文件中配置的节点,获取节点对象。
在这里插入图片描述
在这里插入图片描述
遍历获取的节点,在这里会根据mapper属性中的resource和class来判断是加载xml还是接口配置SQL。如果resource不为空说明这里关联的一个外部文件,则执行xml文件的解析和SQL解析。如果是class,则进行接口注解配置方式的解析。然后通过Resources.classForName(mapperClass)加载类对象。此时就获取到了关联的接口类型。

加载 Mapper 接口

    public <T> void addMapper(Class<T> type) {
        /* Mapper 必须是接口才会注册 */
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // 如果重复添加了,报错
                throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
            }
            // 注册映射器代理工厂
            knownMappers.put(type, new MapperProxyFactory<>(type));

            // 解析注解类语句配置
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
        }
    }

这个方法用于将 Mapper 接口注册到 MyBatis 中。它的目的是确保 Mapper 接口可以通过注解配置 SQL 语句,并且为该接口创建代理对象。
首先检查传入的 type 是否为接口。只有接口才能作为 Mapper 接口在 MyBatis 中注册。因为 MyBatis 使用 Java 的动态代理来为接口生成实现类,如果是类(而非接口),则不能注册。hasMapper(type) 检查该 Mapper 接口是否已经被注册过。如果已经注册,抛出 RuntimeException 异常,避免重复注册同一个接口。这样做是为了防止配置冲突或逻辑错误。
通过 MapperProxyFactory 为指定的 type 接口类型创建代理工厂,并将其存储到 knownMappers 中,knownMappers是一个 Map<Class, MapperProxyFactory> 类型的集合,记录了所有已注册的 Mapper 类型及其对应的代理工厂。
MapperProxyFactory 是 MyBatis 的一个代理工厂类,它负责生成指定 Mapper 接口的代理对象。通过动态代理,MyBatis 可以拦截接口方法调用,并执行相应的 SQL 操作。
MapperAnnotationBuilder 负责解析 Mapper 接口中的注解配置(如 @Select、@Insert 等),并将注解中的 SQL 语句解析并注册到 MyBatis 的 MappedStatement 中。

MapperAnnotationBuilder解析接口方法注解

cn.bugstack.mybatis.builder.annotation.MapperAnnotationBuilder#parse

    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            assistant.setCurrentNamespace(type.getName());

            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if (!method.isBridge()) {
                    // 解析语句
                    parseStatement(method);
                }
            }
        }
    }

parse() 方法的功能是遍历 Mapper 接口中的每个方法,解析其中的 SQL 注解,并将对应的 SQL 语句注册到 MyBatis 的配置中,使得接口方法能够直接执行对应的 SQL 语句。
通过 type.getMethods() 获取当前 Mapper 接口的所有方法(包括继承自 Object 类的方法)。
遍历每个方法,使用 method.isBridge() 排除桥接方法(泛型方法的代理方法)。
对每个非桥接方法,调用 parseStatement(method) 来解析该方法上的 SQL 注解,提取出 SQL 语句,并进行注册。桥接方法是 Java 编译器为支持泛型而生成的特殊方法,通常与实际的业务逻辑无关,所以在解析时被跳过。

parseStatement 方法详解

  /**
     * 解析语句
     */
    private void parseStatement(Method method) {
        Class<?> parameterTypeClass = getParameterType(method);
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

        if (sqlSource != null) {
            final String mappedStatementId = type.getName() + "." + method.getName();
            SqlCommandType sqlCommandType = getSqlCommandType(method);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

            String resultMapId = null;
            if (isSelect) {
                resultMapId = parseResultMap(method);
            }
        }
    }

parseStatement 方法是 MyBatis 中 MapperAnnotationBuilder 类的一个重要方法,用于解析 Mapper 接口中的 SQL 注解(如 @Select、@Insert 等),并根据注解创建 SqlSource 和相关的 SQL 配置(MappedStatement)。
通过 getParameterType(method) 获取当前方法的参数类型。这是为了确定 SQL 执行时需要传入的参数类型,以便正确地构造 SQL 语句。
调用 getLanguageDriver(method) 获取与该方法对应的 LanguageDriver,它用于解析 SQL 语句,并确定如何处理注解中的 SQL。
通过 getSqlSourceFromAnnotations() 方法,根据方法上的 SQL 注解(如 @Select、@Insert)获取 SqlSource。SqlSource 是 MyBatis 中封装 SQL 语句和参数的对象。
通过 type.getName()(Mapper 接口的类名)和 method.getName()(方法名)拼接得到 MappedStatement 的唯一标识符(ID)。这个 ID 用于在 MyBatis 中唯一标识一个 SQL 语句。
通过 getSqlCommandType(method) 获取当前方法对应的 SQL 类型(如 SELECT、INSERT 等)。如果是 SELECT 类型的 SQL,isSelect 设置为 true。
如果 SQL 类型是 SELECT,则通过 parseResultMap(method) 获取查询结果的 ResultMap ID。ResultMap 用于将查询结果的字段映射到 Java 对象。

MapperBuilderAssistant

/**
     * 添加映射器语句
     */
    public MappedStatement addMappedStatement(
            String id,
            SqlSource sqlSource,
            SqlCommandType sqlCommandType,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            LanguageDriver lang
    ) {
        // 给id加上namespace前缀:xxx.test.dao.IUserDao.queryUserInfoById
        id = applyCurrentNamespace(id, false);
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);

        // 结果映射,给 MappedStatement#resultMaps
        setStatementResultMap(resultMap, resultType, statementBuilder);

        MappedStatement statement = statementBuilder.build();
        // 映射语句信息,建造完存放到配置项中
        configuration.addMappedStatement(statement);

        return statement;
    }

addMappedStatement 方法是 MyBatis 中用于创建并注册 SQL 映射语句的核心方法之一。它接受多个参数,用于配置一个完整的 SQL 语句映射并将其添加到 MyBatis 的 Configuration 中.
使用 MappedStatement.Builder 创建一个 MappedStatement 对象。MappedStatement 是 MyBatis 用于封装 SQL 语句信息、参数类型、返回结果等数据的核心对象。
调用 setStatementResultMap 方法,为 MappedStatement 设置结果映射。resultMap 定义了 SQL 查询结果与 Java 对象之间的映射关系。
通过 statementBuilder.build() 方法构建最终的 MappedStatement 对象。此时,MappedStatement 包含了 SQL 语句源、ID、结果类型、命令类型等信息。
将构建完成的 MappedStatement 添加到 MyBatis 的全局配置(configuration)中,确保该 SQL 映射信息被 MyBatis 管理和执行。
这个方法主要用于将从注解解析的 SQL 语句及相关配置注册到 MyBatis 中,使得 MyBatis 能够执行对应的 SQL 语句并正确地映射结果。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[数据结构] 链表

目录 1.链表的基本概念 2.链表的实现 -- 节点的构造和链接 节点如何构造? 如何将链表关联起来? 3.链表的方法(功能) 1).display() -- 链表的遍历 2).size() -- 求链表的长度 3).addFirst(int val) -- 头插法 4).addLast(int val) -- 尾插法 5).addIndex -- 在任意位置…

计算机基础 试题

建议做的时候复制粘贴,全部颜色改为黑色,做完了可以看博客对答案。 一、单项选择题(本大题共25小题,每小题2分,共50分〉 1.计算机内部采用二进制数表示信息,为了便于书写,常用十六进制数表示。一个二进制数0010011010110用十六进制数表示为 A.9A6 B.26B C.4D6 D.…

设计模式12:状态模式

系列总链接&#xff1a;《大话设计模式》学习记录_net 大话设计-CSDN博客 参考&#xff1a;设计模式之状态模式 (C 实现)_设计模式的状态模式实现-CSDN博客 1.概述 状态模式允许一个对象在其内部状态改变时改变其行为。对象看起来像是改变了其类。使用状态模式可以将状态的相…

SmartX分享:NVMe-oF 介绍、SMTX ZBS 如何选择高性能场景解决方案与如何实现

目录 背景什么是 NVMe-oFZBS AccessiSCSI 与 iSERNMVe-oF 介绍NVMeNVMe-oFNVMe-oF 承载网络&#xff08;数据平面&#xff09; ZBS NVMe-oF 实现ZBS 接入策略ZBS 接入点分配策略性能测试 为什么要支持 RoCE引用 背景 前几篇文章&#xff0c;我们认识到了 SmartX 公司产品 SMTX…

数据可视化-1. 折线图

目录 1. 折线图适用场景分析 1. 1 时间序列数据展示 1.2 趋势分析 1.3 多变量比较 1.4 数据异常检测 1.5 简洁易读的数据可视化 1.6 特定领域的应用 2. 折线图局限性 3. 折线图代码实现 3.1 Python 源代码 3.2 折线图效果&#xff08;网页显示&#xff09; 1. 折线图…

python网络框架——Django、Tornado、Flask和Twisted

Django、Tornado和flask是全栈网络框架&#xff0c;而Twisted更专注于网络底层的高性能封装&#xff0c;不提供HTML模版引擎等界面功能&#xff0c;因此不能称为全栈框架。 1、Django 发布于2003年&#xff0c;是当前python世界里最负盛名且最成熟的网络框架。相较于其他web框…

Flash语音芯片相比OTP语音芯片的优势

Flash语音芯片和OTP语音芯片是两种常见的语音解决方案&#xff0c;在各自的应用领域中发挥着重要作用。本文‌将介绍Flash语音芯片相比OTP(One-Time Programmable)语音芯片的显著优势‌。 1‌.可重复擦写‌&#xff1a;Flash语音芯片的最大特点是支持多次编程和擦除&#xff0c…

门店全域推广,线下商家营销布局的增量新高地

门店是商业中最古老的经营业态之一。很早就有行商坐贾的说法&#xff0c;坐贾指的就是门店商家&#xff0c;与经常做商品流通的「行商」相对应。 现在的门店经营&#xff0c;早已不是坐等客来&#xff0c;依靠自然流量吸引顾客上门&#xff0c;大部分的门店经营与推广都已经开…

NX系列-使用 `nmcli` 命令创建 Wi-Fi 热点并设置固定 IP 地址

使用 nmcli 命令创建 Wi-Fi 热点并设置固定 IP 地址 一、前言 在一些场景下&#xff0c;我们需要将计算机或嵌入式设备&#xff08;例如 NVIDIA Orin NX&#xff09;转换为 Wi-Fi 热点&#xff0c;以便其他设备&#xff08;如手机、笔记本等&#xff09;能够连接并使用该设备…

[react] <NavLink>自带激活属性

NavLink v6.28.0 | React Router 点谁谁就带上类名 当然类名也是可以自定义 <NavLinkto{item.link}className{({ isActive }) > (isActive ? 测试 : )}>{item.title}</NavLink> 有什么用?他会监听你的路由,刷新的话也会带上激活效果

【LC】100. 相同的树

题目描述&#xff1a; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&…

代码随想录day24 | leetcode 93.复原IP地址 90.子集 90.子集II

93.复原IP地址 Java class Solution {List<String> result new ArrayList<String>();StringBuilder stringBuilder new StringBuilder();public List<String> restoreIpAddresses(String s) {backtracking(s, 0, 0);return result;}// number表示stringb…

Hive是什么,Hive介绍

官方网站&#xff1a;Apache Hive Hive是一个基于Hadoop的数据仓库工具&#xff0c;主要用于处理和查询存储在HDSF上的大规模数据‌。Hive通过将结构化的数据文件映射为数据库表&#xff0c;并提供类SQL的查询功能&#xff0c;使得用户可以使用SQL语句来执行复杂的​MapReduce任…

AI智能决策赋能服装零售 实现精准商品计划与供需平衡

在服装这个典型的散对散供需模型中&#xff0c;库存问题一直是零售商面临的重大挑战。如何精准预测市场需求&#xff0c;实现供需平衡&#xff0c;成为摆在零售商面前的一道难题。然而&#xff0c;随着智能决策系统的应用&#xff0c;这一切正在悄然改变。 在这个信息爆炸的时代…

RadiAnt DICOM - 基本主题 :从 PACS 服务器打开研究

正版序列号获取&#xff1a;https://r-g.io/42ZopE RadiAnt DICOM Viewer PACS 客户端功能允许您从 PACS 主机&#xff08;图片存档和通信系统&#xff09;搜索和下载研究。 在开始之前&#xff0c;您需要确保您的 PACS 服务器和 RadiAnt 已正确配置。有关配置说明&#xff0c…

VR虚拟展馆如何平衡用户隐私保护与数据收集?

在虚拟现实&#xff08;VR&#xff09;虚拟展馆的设计和运营中&#xff0c;用户隐私保护与数据收集之间的平衡是一个至关重要的议题。 接下来&#xff0c;由专业从事VR虚拟展馆制作的圆桌3D云展厅平台为大家介绍一些策略&#xff0c;可以帮助VR虚拟展馆在收集有用数据的同时&a…

46.全排列 python

全排列 题目题目描述示例 1&#xff1a;示例 2&#xff1a;示例 3&#xff1a;提示&#xff1a; 题解解决方案&#xff1a;回溯算法思路&#xff1a;Python 实现&#xff1a;复杂度分析&#xff1a; 提交结果 题目 题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回…

在Win11系统上安装Android Studio

诸神缄默不语-个人CSDN博文目录 下载地址&#xff1a;https://developer.android.google.cn/studio?hlzh-cn 官方安装教程&#xff1a;https://developer.android.google.cn/studio/install?hlzh-cn 点击Next&#xff0c;默认会同时安装Android Studio和Android虚拟机&#…

基于字节大模型的论文翻译(含免费源码)

基于字节大模型的论文翻译 源代码&#xff1a; &#x1f44f; star ✨ https://github.com/boots-coder/LLM-application 展示 项目简介 本项目是一个基于大语言模型&#xff08;Large Language Model, LLM&#xff09;的论文阅读与翻译辅助工具。它通过用户界面&#xff08…

密钥.id文件连接SSH

不用设置密码&#xff0c;直接连接