写点什么

Java 王者修炼手册【MyBatis 篇 - 底层依赖】:吃透 JDBC/MyBatis 核心接口类,掌控全链路整合逻辑

作者:DonaldCen
  • 2025-12-05
    广东
  • 本文字数:8067 字

    阅读完需:约 26 分钟

Java 王者修炼手册【MyBatis 篇 - 底层依赖】:吃透 JDBC/MyBatis 核心接口类,掌控全链路整合逻辑

大家好,我是程序员强子。

前面文章我们学习了 Mysql,接下来我们趁热打铁,把 Mybatis 相关的底层原理拿下~

其实早就想写一下 Mybatis 了,毕竟我们 CRUD 根本离不开这个框架~

但是呢,想要学透这个框架,很多前置知识需要了解

  • JDBC 基础 接口 & 类

  • MyBatis 基础 接口 & 类

  • SpringBoot 整合 Mybatis 基础 接口 & 类

PS: 文章接下来 可能会出现大量的陌生的类或者接口,没关系,我们先混个眼熟~

对应一个陌生的接口或者类 ,个人认为最重要的是知道它们的作用

而不需要每行代码这样去研究

如果后面涉及到哪个接口或者方法有疑惑,这个时候就可以深入源码~

强子尽量通俗易懂的解释一下每个类或者接口的作用~

并且类出现的顺序是有前后依赖的,请放心食用~

前言

如果小伙伴对 Mybatis 相关源码比较熟悉,可以跳过本文~

为什么要介绍这么多陌生的类 或者 接口呢?

后面我们要学 Mybatis:

  • 多数据源与动态数据源 机制

  • 插件原理

这些底层原理 会大量出现以下的接口 & ,所以先梳理一下前置知识点~

JDBC 基础接口 & 类

DriverManager

核心作用

  • 加载驱动:MySQL 8.0+ 可省略 Class.forName("com.mysql.cj.jdbc.Driver"),驱动会通过 SPI 自动注册;

  • 获取连接:最核心方法是 getConnection(),返回 Connection 实例

Connection

数据库会话句柄,所有 SQL 执行都依赖它;

核心作用

  • 事务管理:默认自动提交(autoCommit=true),关闭后需手动 commit()/rollback();

  • 创建 StatementPreparedStatement:调用 prepareStatement,预编译 SQLStatement: 调用 createStatement ,静态 SQL

PreparedStatement

预编译 SQL 执行器

核心作用

  • 预编译:SQL 模板(如 SELECT * FROM user WHERE id = ?)发送到数据库后预编译,多次执行复用编译结果,性能更高;

  • 防注入:参数通过 setXxx() 绑定,数据库自动转义(如单引号),避免 SQL 注入;

  • 类型安全:支持 setLong()/setString()/setNull() 等方法,适配不同数据类型

// 绑定参数(第1个占位符,值为1L)ps.setLong(1, 1L);// 执行查询,返回 ResultSetResultSet rs = ps.executeQuery();// 执行增删改,返回受影响行数int rows = ps.executeUpdate();
复制代码

ResultSet + ResultSetMetaData

  • ResultSet:存储查询结果,游标默认指向第一行前,需通过 next() 移动游标遍历;

  • ResultSetMetaData:动态获取结果集结构,适配不同表结构。

案例

// 1. 加载驱动(可选)Class.forName("com.mysql.cj.jdbc.Driver");Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;
try {    // 2. 获取连接    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");    conn.setAutoCommit(false); // 关闭自动提交(开启事务)        // 3. 创建预编译 Statement    ps = conn.prepareStatement("SELECT id, name FROM user WHERE id = ?");    ps.setLong(1, 1L); // 绑定参数        // 4. 执行查询,获取结果集    rs = ps.executeQuery();        // 5. 处理结果    while (rs.next()) {        System.out.println("id: " + rs.getLong("id") + ", name: " + rs.getString("name"));    }        conn.commit(); // 提交事务} catch (SQLException e) {    if (conn != null) conn.rollback(); // 异常回滚    e.printStackTrace();} finally {    // 6. 关闭资源(逆序:ResultSet → Statement → Connection)    if (rs != null) rs.close();    if (ps != null) ps.close();    if (conn != null) conn.close();}
复制代码

MyBatis 基础接口 & 类

DataSource

JDBC 规范定义的接口(javax.sql.DataSource)

核心作用

  • 连接管理内部维护一个 数据库连接池(如 HikariCP、Druid)负责连接创建复用销毁,避免频繁创建连接导致的性能开销

  • 连接提供:对外暴露 getConnection() 方法,MyBatis 执行 SQL 时,会从 DataSource获取连接,执行完成后归还连接

常见实现

  • HikariCP(默认,性能最优)

  • Druid(阿里开源,支持监控、防火墙)

  • C3P0(传统连接池,性能较差)

SqlSession

是一个接口~ 是线程不安全

后面会介绍 线程安全版的:SqlSessionTemplate

核心作用

Java 程序与数据库之间的会话管理器,相当于操作数据库的总入口

  • 数据库操作:查、增、删、改

  • 事务管理: commit 提交、rollback 回滚、close 关闭会话释放资源

  • 获取 Mapper 接口代理对象 :返回 Mapper 接口的动态代理实例(即 MapperProxy 代理后的对象

  • 获取配置与执行器:获取 MyBatis 全局配置,获取执行器

简单来说: 所有数据库操作最终都通过 SqlSession 落地

SqlSessionTemplate

线程安全 SqlSession 代理 ,是 Mapper 代理对象执行 SQL 的 核心依赖

核心作用

  • 线程安全封装纯 MyBatis 的 SqlSession 是非线程安全的 SqlSessionTemplate 内部通过 ThreadLocal 绑定当前线程的 SqlSession,确保多线程环境下安全执行。

  • 简化 SqlSession 管理:自动处理 SqlSession 的创建、提交、回滚、关闭,开发者无需手动管理

  • 作为 Mapper 执行入口 Mapper 代理对象的所有方法调用,最终都会委托给 SqlSessionTemplate 由它触发 SqlSession 的 selectOne()、update() 等方法,执行 SQL

SqlSessionFactory

核心作用

  • 存储全局配置:加载并存储 MyBatis 所有配置信息,包括:数据库连接信息(依赖 DataSource);SQL 映射元数据(MappedStatement,来自 Mapper.xml 或 @Select 等注解);全局行为配置(如下划线转驼峰、懒加载、插件等);类型别名、类型处理器等。

  • 创建 SqlSession:提供 openSession() 方法,创建 SqlSession 实例

如何创建

  • 纯 MyBatis 场景:需通过 SqlSessionFactoryBuilder 构建

  • Spring 整合场景:通过 SqlSessionFactoryBean 构建

SqlSessionFactoryBean

FactoryBean 实现类,专门用于在 Spring 环境中构建 SqlSessionFactory

核心作用

  • 整合 Spring 配置:将 DataSourceMyBatis 配置(如 mybatis.configuration.*)、Mapper 映射路径等,统一封装为 SqlSessionFactory 所需的配置。

  • 触发 MyBatis 初始化容器初始化时,调用 SqlSessionFactoryBean.getObject() 方法底层通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory,并将其注册为 Spring Bean。

  • 支持额外配置 setMapperLocations() 配置 Mapper.xml 扫描路径 setPlugins() 注册 MyBatis 插件 setTypeAliasesPackage() 配置实体类别名包

配置 demo

mybatis:  mapper-locations: classpath:mapper/**/*.xml # Mapper.xml 扫描路径  type-aliases-package: com.example.entity # 实体类别名包  configuration:    map-underscore-to-camel-case: true # 下划线转驼峰  plugins: com.github.pagehelper.PageInterceptor # 注册插件
复制代码

MappedStatement

每个 MappedStatement 对应 Mapper.xml 中的一个

  • select 标签

  • insert 标签

  • update 标签

  • delete 标签

  • 一个注解 SQL

来跟强子了解一下这个类的作用~

存储 SQL 元数据

  • SQL 语句本身:可能是原生 SQL,也可能是带占位符 #{} 的动态 SQL

  • SQL 类型:包括 SELECT/INSERT/UPDATE/DELETE;

  • 参数类型: ParameterMap,比如 User 类或 Integer;

  • 结果类型:ResultMap,比如返回 User 类或 List;

  • 其他配置:如是否使用缓存、超时时间、fetchSize 等

是不是觉得很眼熟 平常 xml 里面的 sql,组成部分就是包括以上这些~

为 SQL 执行提供「依据」

  • MapperProxy 拦截到 Mapper 接口方法调用后,

  • 会根据「接口全类名 + 方法名」找到对应 MappedStatement

  • 传递给 MyBatis 底层的执行器(Executor

  • 执行器通过 MappedStatement 中的信息,就能知道要执行什么 SQL 传什么参数返回什么结果

BoundSql

运行时最终可执行 SQL 的载体

每次执行 Mapper 方法时,由 MappedStatement 根据实际参数动态生成

具体作用

  • 存储最终可执行 SQL 解析动态 SQL(if/foreach/where 等)后生成的、无动态标签的纯 SQL 字符串不过是包含 ? 占位符

  • 维护参数映射元信息存储 ParameterMapping 集合每个 ParameterMapping 对应一个 #{} 占位符,包含参数名、参数类型、类型处理器等

  • 提供参数值入口通过 getParameterObject() 返回绑定的参数对象(如 @Param 封装的 ParamMap),供参数绑定使用

  • 辅助 JDBC 参数绑定 MyBatis 基于 BoundSql 中的 SQL 和 ParameterMapping,将参数值设置到 PreparedStatement 中

调用链路

MapperProxy.invoke() →

MapperMethod.execute() →

SqlSession.selectOne() → MappedStatement.getBoundSql(parameterObject)

案例

public class BoundSql {
    private final String sql;    private final List<ParameterMapping> parameterMappings;    private final Object parameterObject;    private final Map<String, Object> additionalParameters;    private final MetaObject metaParameters;
    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {        this.sql = sql;        this.parameterMappings = parameterMappings;        this.parameterObject = parameterObject;        this.additionalParameters = new HashMap<>();        this.metaParameters = configuration.newMetaObject(additionalParameters);    }    // getter setter ..}
复制代码

假设 Mapper 接口方法:

User selectUser(@Param("id") Long id, @Param("name") String name);
复制代码

XML 中的动态 SQL:

<select id="selectUser" resultType="User">  SELECT * FROM user  <where>    <if test="id != null">AND id = #{id}</if>    <if test="name != null">AND name = #{name}</if>  </where></select>
复制代码

当调用 selectUser(1L, "test") 时,生成的 BoundSql 核心内容:

// BoundSql 核心属性{    sql = "SELECT * FROM user WHERE id = ? AND name = ?", // 动态解析后的最终SQL    parameterMappings = [    ParameterMapping{name='id', jdbcType=null, ...}, // 对应#{id}的参数映射    ParameterMapping{name='name', jdbcType=null, ...} // 对应#{name}的参数映射    ],    parameterObject = ParamMap{"id":1L, "name":"test", "param1":1L, "param2":"test"}, // @Param封装的参数对象    additionalParameters = {}, // 额外参数(如动态SQL生成的临时参数)    metaParameters = null}
复制代码

MapperProxy

小伙伴你们是不是会跟强子一样好奇:

为什么调用无实现类的 Mapper 接口,能注入,也能实现直接调用方法执行 SQL ?? 此处应该有 黑人问号脸.jpg

没错,其中的答案就是: MapperProxy !!!

特点

  • 一个动态代理类,基于 JDK 动态代理实现

  • 负责拦截 Mapper 接口的所有方法调用,将其转化为 SqlSession 的 SQL 执行操作。

PS:

动态代理我们前面文章学过,没有学过的小伙伴回去学习下~

工作流程

获取 UserMapper 的时候(注入 或者 手动调用)

  • MyBatis 不会创建 UserMapper 的实现类

  • 而是通过 MapperProxyFactory 生成一个 MapperProxy 代理对象(伪装成 UserMapper 实例)

  • 开发者拿到的其实是这个代理对象

当调用 userMapper.getUserById(666) 时

  • 代理对象 MapperProxy拦截方法调用

  • 解析方法信息,组成 SQL 的唯一标识(即 MappedStatement 的 id)

  • 提取方法参数:获取传入的参数(比如 666);

  • 找到对应 MappedStatement:根据 SQL 唯一标识,从 MyBatis 全局配置(Configuration)中找到对应的 MappedStatement;

  • 调用 SqlSession 执行 SQL:通过 SqlSession 的 selectOne()/selectList() 等方法,传入 MappedStatement 和参数,执行 SQL 并返回结果。

ParameterHandler

参数处理器,SQL 参数绑定核心

核心作用

  • 解析参数映射元信息:从 BoundSql 中读取 ParameterMapping 集合(每个 ParameterMapping 对应 #{xxx} 占位符,包含参数名、类型、TypeHandler 等);

  • 提取参数值:从 MyBatis 封装的参数对象(如 ParamMap、自定义 User 对象)中,通过反射 / 元对象(MetaObject)提取对应参数值;

  • 类型转换与绑定:通过 TypeHandler 将 Java 类型转换为 JDBC 类型,设置到 PreparedStatement 的对应占位符(?)中;

  • 处理特殊场景:如 null 值、数组 / 集合参数、存储过程输出参数等的绑定逻辑

核心实现类

提供一个默认实现 DefaultParameterHandler

扩展需求通过 TypeHandler 实现

关键特性

  • 依赖 TypeHandler:是 Java 类型与 JDBC 类型转换的核心(如 Long → BIGINT、String → VARCHAR);

  • 兼容多参数场景:无论是 @Param 封装的 ParamMap,还是自定义对象,都能通过 MetaObject 统一提取值;

  • 防 SQL 注入:底层依赖 PreparedStatement.setXxx(),参数自动转义,避免注入风险

ResultSetHandler

结果集处理器,结果映射核心

核心作用

  • 解析结果配置:从 MappedStatement 中读取 resultType/ResultMap/resultSetType 等配置;

  • 遍历结果集:逐行处理 JDBC ResultSet,将每行数据映射为指定的 Java 对象;

  • 处理复杂嵌套结果映射,支持:一对一(association)一对多(collection)鉴别器(discriminator

  • 资源清理:自动关闭 ResultSet,避免数据库资源泄漏;

  • 适配返回类型:根据配置返回单个对象、List、Map 等不同类型结果。

RowBounds

定义

MyBatis 内置的内存分页(逻辑分页)工具类

核心通过 offset(起始偏移量)和 limit(每页条数)两个参数

在查询结果集层面完成分页截取,无需修改 SQL 语句;

案例

// 1. 基础使用:查询第1页,每页10条数据(内存分页)RowBounds rowBounds = new RowBounds(0, 10); List<User> userList = userMapper.selectList(new QueryWrapper<User>().eq("age", 18), rowBounds);
// 2. 结合MyBatis-Plus条件构造器+RowBoundsLambdaQueryWrapper<User> lambdaQuery = Wrappers.lambdaQuery(User.class).ge(User::getAge, 18);List<User> list = userMapper.selectList(lambdaQuery, new RowBounds(10, 10)); // 第2页,每页10条
复制代码

使用局限性

  • 性能瓶颈:RowBounds 是内存分页;数据量超过 1000 条时会导致内存占用高、查询耗时久,生产环境严禁用于大数据量分页

  • 优先级低于物理分页 MyBatis-Plus 官方明确推荐使用 IPage+PaginationInnerInterceptor(物理分页,改写 SQL 添加 LIMIT/OFFSET)仅在小数据量 / 兼容场景下使用 RowBounds

  • 不支持复杂分页无法获取总条数、总页数等分页元数据仅能获取当前页数据,需手动查询总条数(selectCount(queryWrapper)),不如 IPage 便捷。

核心实现类

MyBatis 仅提供默认实现 DefaultResultSetHandler

支持所有主流结果映射场景

  • 简单映射

  • 复杂嵌套

  • 批量结果

关键特性

  • 自动映射 vs 手动映射自动映射(autoMapping=true):按列名 / 别名直接匹配 Java 对象属性名,无需配置 ResultMap;手动映射:按 ResultMap 中 column(数据库列)→ property(Java 属性)的配置精准映射;

  • 嵌套结果映射:通过 association/collection 处理关联查询结果,无需额外 SQL 调用;

  • 延迟加载:支持嵌套结果的懒加载(按需加载关联数据),底层通过 ResultLoaderMap 实现;

  • 依赖 TypeHandler:将 JDBC 类型转换为 Java 类型

SpringBoot + Mybatis 整合基础

DataSourceAutoConfiguration

SpringBoot 自动配置数据库连接池(数据源)的核心类

负责创建 DataSource 实例(数据库连接的核心对象)

拥有数据源,MyBatis 就才能连接数据库

具体作用

  • 读取数据源配置:自动读取 application.yml/properties 中 spring.datasource.* 前缀的配置(如 url、username、password、driver-class-name);

  • 自动创建连接池默认使用 SpringBoot 内置的 HikariCP 连接池也支持 Druid、Tomcat-JDBC 等第三方连接池无需手动配置连接池 Bean

  • 注册数据源到 Spring 容器:将创建好的 DataSource 对象注册为 Spring 单例 Bean,供后续 MyBatis 组件(如 SqlSessionFactory)依赖使用;

配置 demo

spring:  datasource:    url: jdbc:mysql://localhost:3306/test    username: root    password: 123456    driver-class-name: com.mysql.cj.jdbc.Driver
复制代码

MyBatisAutoConfiguration

基于 DataSourceAutoConfiguration 创建的数据源

自动初始化 MyBatis 核心组件

  • SqlSessionFactory

  • SqlSessionTemplate

并且注册到 Spring 容器 ~

具体作用

  • 依赖数据源创建 SqlSessionFactory:注入 DataSource,并结合 mybatis.* 配置 ,构建 SqlSessionFactory Bean

  • **读取 MyBatis 自定义配置 **:比如指定 Mapper.xml 路径、实体类别名包、配置文件位置等

  • 创建线程安全的 SqlSessionTemplate:原生 SqlSession 线程不安全,该类会自动创建 SqlSessionTemplate(SqlSession 的线程安全封装)并注册为 Spring Bean 这是 SpringBoot 中 MyBatis 实际使用的会话对象

  • 支持自定义扩展允许开发者通过自定义 SqlSessionFactoryBeanMyBatisConfigurer 等,覆盖默认配置比如添加分页插件自定义类型处理器

MapperScannerConfigurer

Spring 提供的 BeanPostProcessor 实现类

纯 Spring 场景下的「手动扫描注册器

负责扫描项目中的 Mapper 接口,将其注册为 Spring BeanDefinition

手动配置(XML/JavaConfig)才能生效

核心作用

  • 扫描 Mapper 接口:根据配置的扫描路径,扫描包下所有符合条件的接口(被 @Mapper 标记或在 @MapperScan 路径下)

  • 注册 BeanDefinition:对每个扫描到的 Mapper 接口,创建 BeanDefinition,指定实例化方式 为 MapperFactoryBean 通过 MapperFactoryBean 生成 Mapper 代理对象

  • 注入依赖:在 BeanDefinition 中注入 SqlSessionFactorySqlSessionTemplate,确保 MapperFactoryBean 能生成有效的 Mapper 代理对象

MapperScannerAutoConfiguration

自动扫描指定包下Mapper 接口,为每个接口生成 MapperProxy 动态代理对象,并注册到 Spring 容器

SpringBoot 场景下的「自动配置类」,封装 MapperScannerConfigurer 的创建

自动触发(满足条件时),无需手动配置

可以直接 @Autowired 注入 Mapper 接口使用

具体作用

  • 扫描指定包下Mapper 接口:读取配置中的 mybatis.mapper-locations 或 @MapperScan 注解指定的包路径,扫描该路径下所有的 Mapper 接口

  • 生成 MapperProxy 代理对象:对每个扫描到的 Mapper 接口,底层通过 MapperProxyFactory 生成 MapperProxy 代理对象

  • 注册代理对象到 Spring 容器:将生成的代理对象注册为 Spring Bean,使得可以直接通过 @Autowired 注入使用

后续可以直接注入 Mapper 接口

@Servicepublic class UserService {    // 直接注入,无需手动创建代理对象    @Autowired    private UserMapper userMapper;}
复制代码

总结

  1. DataSourceAutoConfiguration 先执行读取数据源配置创建 DataSource Bean 并注册;

  2. MyBatisAutoConfiguration 依赖 DataSource 创建 SqlSessionFactorySqlSessionTemplate Bean;

  3. MapperScannerAutoConfiguration 扫描指定包下的 Mapper 接口基于 SqlSessionTemplate 生成 MapperProxy 代理对象注册到 Spring 容器;

  4. 最终效果:开发者只需注入 Mapper 接口即可

JDBC vs Mybatis

总结

今天我们 复习+回顾了 各种各样的 接口 & 类

  1. 首先我们简单回顾一下 **JDBC 相关 **接口 & 类

  2. 接下来 我们了解 Mybatis 相关的 接口 & 类

  3. 顺便复习 一下 SpringBoot + Mybatis 整合的关键类

  4. 最后 JDBCMyabtis 对比 关联

这些对后续我们要搞懂 Mybatis 底层原理非常重要,

所以今天强子费了好大劲,搜集了这些相关的 接口 & 类,对比着学习~

下篇文章强子准备专门把 Mybatis 的以下机制搞定:

  • 多数据源与动态数据源

  • 插件原理

感兴趣的可以关注一下~

如果觉得帮到你们,烦请 点赞关注推荐 三连,感谢感谢~~

熟练度刷不停,知识点吃透稳,下期接着练~

发布于: 刚刚阅读数: 2
用户头像

DonaldCen

关注

有个性,没签名 2019-01-13 加入

跟我在峡谷学Java 公众号:程序员悟空的宝藏乐园

评论

发布
暂无评论
Java 王者修炼手册【MyBatis 篇 - 底层依赖】:吃透 JDBC/MyBatis 核心接口类,掌控全链路整合逻辑_connection_DonaldCen_InfoQ写作社区