Mybatis源码基本原理--XML版

news2025/6/5 19:29:32

文章目录

  • mybatis
    • 是什么
    • 架构设计
    • 首先建立起Mapper的代理工程和代理
    • 映射器的注册和使用
    • XML文件解析
    • 数据源解析、创建和使用
    • SQL执行器(Executor)的定义与实现
    • SQL解析
    • 参数处理器:策略模式实现
    • 封装处理结果
    • 注解

mybatis

是什么

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
——来自官方文档

架构设计

在这里插入图片描述

在这里插入图片描述

通过XMLConfigBuilder解析xml文件放到Configuration对象中

首先建立起Mapper的代理工程和代理

代理类

package cn.mybatis.binding;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;

    private Map<String, String> sqlSession;
    private final Class<T> mapperInterface;

    public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        if(Object.class.equals(method.getDeclaringClass())){
            return method.invoke(this, objects);
        }else {
            System.out.println("你被代理了");
            return sqlSession.get(mapperInterface.getName() + "." + method.getName());
        }
    }
}

代理工厂

package cn.mybatis.binding;

import java.lang.reflect.Proxy;
import java.util.Map;

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(Map<String, String> sqlSession){
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}

被代理的接口

package cn.mybatis.dao;

public interface IUserDao {
    public String queryUserName(String Id);

}

测试类

  @Test
    public void test_MapperProxyFactory() {
        MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
        Map<String, String> sqlSession = new HashMap<>();

        sqlSession.put("cn.mybatis.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
        IUserDao userDao = factory.newInstance(sqlSession);

        String res = userDao.queryUserName("10001");
        System.out.println(res);
    }

上面代码中sqlSession有所需要执行的sql

映射器的注册和使用

定义一个MapperRegistry,内部定义了一个Map
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
Class<?>代表着执行sql的接口 MapperProxyFactory<?>是相应的代理工厂
它会扫描所有的接口类,放到这个map中,并且最终会被Configuration调用

SqlSession、DefaultSqlSession 用于定义执行 SQL 标准、获取映射器以及将来管理事务等方面的操作。基本我们平常使用 Mybatis 的 API 接口也都是从这个接口类定义的方法进行使用的。
SqlSessionFactory 是一个简单工厂模式,用于提供 SqlSession 服务,屏蔽创建细节,延迟创建过程。

XML文件解析

需要定义 SqlSessionFactoryBuilder 工厂建造者模式类,通过入口 IO 的方式对 XML 文件进行解析。
文件解析以后会存放到 Configuration 配置类中,接下来你会看到这个配置类会被串联到整个 Mybatis 流程中,所有内容存放和读取都离不开这个类
通过 Configuration 配置类进行存放,包括:添加解析 SQL、注册Mapper映射器。
比较重要的一个是上面提到的MapperRegistry
还定义了一个重要的 protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();
是本章节新添加的 SQL 信息记录对象,包括记录:SQL类型、SQL语句、入参类型、出参类型等
TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); 类型别名注册机

这个Configuration最终会放到 DefaultSqlSession里

数据源解析、创建和使用

以事务接口 Transaction 和事务工厂 TransactionFactory 的实现,包装数据源 DruidDataSourceFactory 的功能。
当所有的数据源相关功能准备好后,就是在 XMLConfigBuilder 解析 XML 配置操作中,对数据源的配置进行解析以及创建出相应的服务,存放到 Configuration 的环境配置中。
最后在 DefaultSqlSession#selectOne 方法中完成 SQL 的执行和结果封装,最终就把整个 Mybatis 核心脉络串联出来了。
一次数据库的操作应该具有事务管理能力,而不是通过 JDBC 获取链接后直接执行即可。还应该把控链接、提交、回滚和关闭的操作处理。所以这里我们结合 JDBC 的能力封装事务管理。
通过环境构建 Environment.Builder 存放到 Configuration 配置项中,也就可以通过 Configuration 存在的地方都可以获取到数据源了。

SQL执行器(Executor)的定义与实现

回顾一下流程:
DefaultSqlSessionFactor开启operSession,并随着构造参数传递给DefaultSqlSession,并执行DefaultSqlSession#selectOne方法就会调用执行器执行

来看下执行器的操作:
1、定义一个Executor接口,这个接口定义了执行方法、事务获取和相应提交、回滚、关闭
2、在定义BaseExecutor一个抽象工厂进行模板方法的初步定义
3、SimpleExecutor会实现抽像接口进行具体执行,执行实际上是调用StatementHandler接口的具体实现来执行sql的
上面的执行器就完成了
再说下语句处理器:StatementHandler
语句处理器的核心包括了;准备语句、参数化传递参数、执行查询的操作,这里对应的 Mybatis 源码中还包括了 update、批处理、获取参数处理器等。
1、StatementHandler接口定义了具体执行sql的代码基本方法,包括准备语句、参数化传递参数、执行查询的操作
2、BaseStatementHandler 抽象基类实现了StatementHandler,并且增加定义了一系列的抽象方法交给其子类来处理
3、PreparedStatementHandler 预处理语句处理器继承BaseStatementHandler,进行具体的处理,包括 instantiateStatement 预处理 SQL、parameterize 设置参数,以及 query 查询的执行的操作。

SQL解析

解析、绑定、映射、事务、执行、数据源
SQL解析实际是解析XML或者注解中的SQL,并且将绑定的方法中的参数映射到SQL的过程
以XML为例解析:
XMLMapperBuilder、XMLStatementBuilder 分别处理映射构建器和语句构建器
1、映射构建器XMLMapperBuilder:
parse()方法中会间接调用configuration.addMapper 绑定映射器主要是把 namespace(全路径的接口类) 绑定到 Mapper 上。也就是注册到映射器注册机里。
具体实现是:

 mapperRegistry.addMapper(type);
 //核如下
 knownMappers.put(type, new MapperProxyFactory<>(type));  type是具体的全路径接口类

2、映射构建器:XMLStatementBuilder
XMLStatementBuilder 语句构建器主要解析 XML 中 select|insert|update|delete 中的语句
parseStatementNode方法会解析各个参数
包括了语句的ID、参数类型、结果类型、命令(select|insert|update|delete),以及使用语言驱动器处理和封装SQL信息,当解析完成后写入到 Configuration 配置文件中的 Map<String, MappedStatement> 映射语句存放中。

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

3、XML脚本构建器解析:XMLScriptBuilder
XMLScriptBuilder#parseScriptNode 解析SQL节点的处理其实没有太多复杂的内容,主要是对 RawSqlSource 的包装处理。
4、SQL源码构建器:SqlSourceBuilder

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql;
    //获取sql
    if (configuration.isShrinkWhitespacesInSql()) {
      sql = parser.parse(removeExtraWhitespaces(originalSql));
    } else {
      sql = parser.parse(originalSql);
    }
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

参数处理器:策略模式实现

上面实现了解析 XML 中的所需要处理的 Mapper 信息,包括;SQL、入参、出参、类型,并对这些信息进行记录到 ParameterMapping 参数映射处理类中
这里将会使用一个接口实现ps.setXxx(i, parameter);对于所有的类型的支持
关于参数的处理,因为有很多的类型(Long\String\Object…),所以这里最重要的体现则是策略模式的使用
核心处理主要分为三块:类型处理、参数设置、参数使用;
1、以定义 TypeHandler 类型处理器策略接口,实现不同的处理策略,包括;Long、String、Integer 等
定义接口:

public interface TypeHandler<T> {

    /**
     * 设置参数
     */
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}

定义模板

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
f (parameter == null) {

      ps.setNull(i, jdbcType.TYPE_CODE);
    } else {
       setNonNullParameter(ps, i, parameter, jdbcType);
    }
    protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

}

这里把一些异常处理都给去掉了,保留基本的逻辑。
当传入的参数不为空的时候,可以直接 ps.setNull(i, jdbcType.TYPE_CODE);,当为空的时候就交给具体的子类来处理
子类实现:

public class StringTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

Mybatis 源码中还有很多其他类型
2、类型策略处理器实现完成后,需要注册到处理器注册机(TypeHandlerRegistry )中,其他模块参数的设置还是使用都是从 Configuration 中获取到 TypeHandlerRegistry 进行使用。

public final class TypeHandlerRegistry {

    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>();
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();

    public TypeHandlerRegistry() {
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());

        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    }
 
    //...
}

这里在构造函数中,新增加了 LongTypeHandler、StringTypeHandler 两种类型的注册器。
3、了这样的策略处理器以后,在进行操作解析 SQL 的时候,就可以按照不同的类型把对应的策略处理器设置到 BoundSql#parameterMappings 参数里
这里主要通过反射的方式获取参数类型,然后 if 判断对应的参数类型是否在 TypeHandlerRegistry 注册器中,如果不在则拆解对象,按属性进行获取 propertyType 的操作。

4、参数使用
那么这里的链路关系;Executor#query - > SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters 到了 ParameterHandler#setParameters 就可以看到了根据参数的不同处理器循环设置参数。
每一个循环的参数设置,都是从 BoundSql 中获取 ParameterMapping 集合进行循环操作
设置参数时根据参数的 parameterObject 入参的信息,判断是否基本类型,如果不是则从对象中进行拆解获取(也就是一个对象A中包括属性b),处理完成后就可以准确拿到对应的入参值了。
基本信息获取完成后,则根据参数类型获取到对应的 TypeHandler 类型处理器,也就是找到 LongTypeHandler、StringTypeHandler 等,确定找到以后,则可以进行对应的参数设置了 typeHandler.setParameter(ps, i + 1, value, jdbcType) 通过这样的方式把我们之前硬编码的操作进行解耦。

封装处理结果

主要是针对JDBC查出来结果映射到标签resultType中去。
我们拿到了 Mapper XML 中所配置的返回类型,解析后把从数据库查询到的结果,反射到类型实例化的对象上。
MapperBuilderAssistant 映射器的助手类,方便我们对参数的统一包装处理,按照职责归属的方式进行细分解耦。
在执行完成sql后得到一个结果会在 DefaultResultSetHandler 进行信息封装
1、对象创建
调用链路:handleResultSet->handleRowValuesForSimpleResultMap->getRowValue->createResultObject
关键代码

 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType);
    if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
        // 普通的Bean对象类型
        return objectFactory.create(resultType);
    }
    throw new RuntimeException("Do not know how to create an instance of " + resultType);
}

2、属性填充
对象实例化完成后,就是根据 ResultSet 获取出对应的值填充到对象的属性中,但这里需要注意,这个结果的获取来自于 TypeHandler#getResult 接口新增的方法,由不同的类型处理器实现,通过这样的策略模式设计方式就可以巧妙的避免 if···else 的判断处理。
columnName 是属性名称,根据属性名称,按照反射工具类从对象中获取对应的 properyType 属性类型,之后再根据类型获取到 TypeHandler 类型处理器。有了具体的类型处理器,在获取每一个类型处理器下的结果内容就更加方便了。
获取属性值后,再使用 MetaObject 反射工具类设置属性值,一次循环设置完成以后,这样一个完整的结果信息 Bean 对象就可以返回了。返回后写入到 DefaultResultContext#nextResultObject 上下文中

注解

MapperAnnotationBuilder中处理注解,这个类在构造函数中配置需要解析的注解,并提供解析方法处理语句的解析。
整个类基本基于Method来获取参数类型、返回类型和注解类型,并完成整个解析过程。

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

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

相关文章

中国5米分辨率坡度数据

中国5米分辨率坡度数据 坡度是地表单元陡缓的程度&#xff0c;通常把坡面的垂直高度和水平距离的比值称为坡度。坡度的表示方法有百分比法、度数法、密位法和分数法四种&#xff0c;其中以百分比法和度数法较为常用。 中国5米分辨率坡度数据集&#xff0c;利用5米分辨率DEM数据…

多肉植物,预计到2025我国市场规模将达到140亿元人民币

多肉植物是一种新兴的盆栽植物&#xff0c;由于造型各异、易于养殖、低维护难度等优点&#xff0c;在全球市场和中国市场受到了越来越多消费者的追捧。全球市场分析 从全球市场来看&#xff0c;多肉植物市场规模正在逐步扩大。各种形态各异的多肉植物受到消费者的喜爱&#xff…

trino 433 开启 HTTPS

什么要开启https 因为开始password验证要求必须得https。 摘要 trino节点之间可以不用开启SSL&#xff0c;对外访问开启SSL。如果自备证书可以直接配置到trino的config文件&#xff0c;如果没有证书可以使用mkcert生成自签证书&#xff08;客户端需要信任证书&#xff0c;尤…

【Leetcode 2487】从链表中移除节点 —— 单调栈

2487. 从链表中移除节点 给你一个链表的头节点head。 移除每个右侧有一个更大数值的节点。 返回修改后链表的头节点head。 示例 1&#xff1a; 输入&#xff1a;head [5,2,13,3,8] 输出&#xff1a;[13,8] 解释&#xff1a;需要移除的节点是 5 &#xff0c;2 和 3 。 节点 1…

aliexpress商品API(item_get-获得aliexpress商品详情):进行批量操作

使用AliExpress的店铺或分类API&#xff1a;这些API可以为你提供某个店铺或分类下的所有商品列表&#xff0c;然后你可以根据这个列表逐个查询商品详情。分批查询&#xff1a;你可以将商品ID分成多个批次&#xff0c;每次只查询一部分商品详情&#xff0c;这样既可以减少每次请…

如何搭建中后台管理系统

vue3 TS vite 搭建中后台管理系统 前言1、搭建步骤及方法2、集成多种插件功能&#xff0c;实现中后台按需使用3、新手学TS如何快速进入状态、定义TS类型4、layout搭建四款常见风格6、大屏搭建效果5、vue3Ts运营管理系统总结&#xff1a; 前言 要成功&#xff0c;先发疯&…

制造企业如何打破“信息孤岛”,跑赢从制造到“智造”的破局之路?

随着工业4.0时代到来&#xff0c;制造业乘上了智能制造发展的快车&#xff0c;但“乘客”却偏少。普华永道发布的《2022年数字化工厂转型调研报告》中指出&#xff0c;来自23个国家和地区的700多家受访企业中&#xff0c;只有10%的企业已经完成数字化转型计划或处于转型最后阶段…

快速打通 Vue 3(二):响应式对象基础

很激动进入了 Vue 3 的学习&#xff0c;作为一个已经上线了三年多的框架&#xff0c;很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue&#xff08;一&#xff09;&#xff1a;Vue 入…

Prometheus插件安装(NodeExporter)二进制安装包安装

一&#xff0c;下载安装包并解压 **下载地址&#xff1a;**https://github.com/prometheus/node_exporter/releases 同样物理机上下载&#xff0c;然后上传到服务器&#xff0c;本次安装使用的版本为&#xff1a;node_exporter-1.5.0.linux-amd64 1&#xff0c;根据服务器情况…

2024上海城博会|上海国际城市与建筑博览会-官 网

2024上海城博会|上海国际城市与建筑博览会 时间&#xff1a;2024年10月30日-11月1日 地点&#xff1a;上海世博展览馆 主办单位&#xff1a;联合国人居署 上海市住房和城乡建设管理委员会 协办单位&#xff1a;上海世界城市日事务协调中心 展会介绍 上海国际城市与建筑博览…

AI:110-基于深度学习的药物分子结构生成与预测

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

如何写出一份优秀的简历?(求职必知)

你需要知道的事 简历是对自己职场的总结和概括&#xff0c;是通往下一段职业经历的敲门砖和 垫脚石。 因此&#xff0c;一份好的简历应该突出应聘者的优势&#xff0c;并引起企业方的好奇心。 知己知彼&#xff0c;百战百胜&#xff0c;求职者只有了解自己&#xff0c;以及了解…

vue-mixins混入处理

定义 mixins&#xff08;混入&#xff09;&#xff1a;一种分发 Vue 组件中可复用功能的非常灵活的方式&#xff0c;mixins 是一个 js 对象&#xff0c;它可以包含我们组件script中的任意功能选项&#xff0c;如&#xff1a;data、components、methods、created、computed 等等…

Avalonia学习(十七)-CEF

今天开始继续Avalonia练习。 本节&#xff1a;CefNet 1.引入 CefNet.Avalonia.Eleven 2.项目引入 Program中加入 using Avalonia; using Avalonia.ReactiveUI; using Avalonia.Threading; using CefNet; using System; using System.IO; using System.Linq; using System…

力扣刷题记录(25)LeetCode:583、72、647

583. 两个字符串的删除操作 题目说可以删除任意一个字符串中的字符&#xff0c;实际上就是在求两个字符串的公共子序列。求得公共子序列后与字符串长度做个减法即可得需要的步数。 class Solution { public://求最长子数组int minDistance(string word1, string word2) {vecto…

基于双向LSTM模型完成文本分类任务

6.4.1 数据处理 IMDB电影评论数据集是一份关于电影评论的经典二分类数据集&#xff0e;IMDB 按照评分的高低筛选出了积极评论和消极评论&#xff0c;如果评分 ≥7≥7&#xff0c;则认为是积极评论&#xff1b;如果评分 ≤4≤4&#xff0c;则认为是消极评论&#xff0e;数据集包…

艾思控AQMD6008BLS-TE无刷电机驱动使用笔记(配合STM32)

一、介绍 本驱动器使用的电机电流精确检测技术、有感无刷电机自测速、有感无刷电机转动位置检测、再生电流恒电流制动&#xff08;或称刹车&#xff09;技术和强大的PID调节技术可地控制电机平稳正反转、换向及制动&#xff0c;输出电流实时调控防止过流&#xff0c;精准控制电…

「许战海战略文库」佳隆股份:2亿级别的调味品公司如何应对增长难题

自2002年以来&#xff0c;佳隆食品逐步向集团化方向发展&#xff0c;2010年11月2日在深圳证券交易所成功挂牌上市。 2009年-2022年&#xff0c;公司营收增长并不明显&#xff0c;基本维持在2-3亿之间。尤其是2022年&#xff0c;营收出现亏损的情况&#xff0c;在运营和增长战略…

ruoyi-ai 基于ruoyi-plus实现AI聊天和绘画功能-后端

基于ruoyi-plus实现AI聊天和绘画功能-后端 本项目完全开源免费&#xff01; 后台管理界面使用elementUI服务端使用Java17SpringBoot3.X ruoyi-ai: 基于ruoyi-plus实现AI聊天和绘画功能-后端 实现功能 集成OpenAi API (gpt-4-vision-preview dall-e-3)接入文生图模型&#xf…

satellite-image-deep-learning,一个遥感深度学习方向的宝藏网站

今天我们分享一个深度学习遥感相关的网站&#xff1a;「satellite-image-deep-learning」。这是一个github库&#xff0c;里面含有大量应用于卫星和航空图像的深度学习资源。 主要包括以下几个方面&#xff1a; annotation&#xff1a;提供数据集注释信息&#xff0c;里面包含…