MyBatis 核心

共 34 题
📑 题目列表 34 题
#
★★★

1. #{} 与 ${} 的 SQL 注入与预编译

请说明 MyBatis 中 #{} 与 ${} 的区别,以及它们与 SQL 注入、预编译的关系?

  • #{} 使用 PreparedStatement 参数占位符
  • ${} 直接字符串拼接
  • 防注入与灵活性的权衡

MyBatis 中 #{} 会解析为 PreparedStatement 的占位符(?),参数值由 JDBC 预编译绑定,可防止 SQL 注入;${} 直接进行字符串替换,把值拼进 SQL 文本,存在 SQL 注入风险,只在表名、列名、排序字段等无法参数化的场景使用并需严格校验。预编译(PreparedStatement)将 SQL 结构与参数分离,参数作为数据传递,恶意输入无法改变 SQL 结构,因此 #{} 是安全的。${} 还会使 SQL 无法被缓存预编译,影响性能。

安全优先原则是默认用 #{},${} 仅限必须动态指定标识符(表名/列名/排序/分页)且经过白名单校验。理解预编译机制是防注入的核心。

-- 安全:参数化
SELECT * FROM t_user WHERE name = #{name}
-- 危险:字符串拼接,name 可注入 SQL
SELECT * FROM t_user WHERE name = ${name}
#
★★★

2. MyBatis 一级缓存(SqlSession 级别)与二级缓存(Mapper 级别)的失效场景

请说明 MyBatis 一级缓存与二级缓存的作用域及失效场景?

  • 一级缓存(SqlSession 级)的生命周期
  • 二级缓存(Mapper 级)的共享
  • 缓存失效的触发

MyBatis 一级缓存是 SqlSession 级别的(默认开启),同一个 SqlSession 内相同的查询结果缓存,但任何增删改操作(update)会清空该 SqlSession 的缓存,且不同 SqlSession 之间不共享。一级缓存作用域小,跨 SqlSession 仍可能读到旧数据。二级缓存是 Mapper(namespace)级别的,跨 SqlSession 共享,需在配置中显式开启,通过缓存实现(如 PerpetualCache、Redis)存储。二级缓存失效场景包括:任何对同 namespace 的增删改操作、配置了 flushCache、以及跨 namespace 的关联查询产生脏读。二级缓存还存在分布式一致性风险。

一级缓存是"会话内"事务性缓存,二级缓存是"namespace 级"进程缓存。二级缓存可能因多实例、跨表更新导致脏读,需谨慎使用。理解失效场景可避免读到过期数据。

#
★★★

3. MyBatis 二级缓存接入 Redis 等外部缓存时的一致性风险(脏读/过期策略)

请说明 MyBatis 二级缓存接入 Redis 等外部缓存时的一致性风险(脏读、过期策略)?

  • 外部缓存的一致性问题
  • 脏读的成因
  • 过期与失效策略

将 MyBatis 二级缓存接入 Redis 等外部缓存,多实例可共享缓存,但会引入一致性问题:同一 namespace 的缓存与数据库更新可能不同步,跨实例的更新无法及时通知其他实例清缓存,导致脏读。此外,MyBatis 二级缓存粒度是 namespace(Mapper),无法感知跨表更新,一个表被其他 Mapper 修改后,本 namespace 的缓存仍可能返回旧值。过期策略(TTL、flushInterval)只能缓解无法根治。需要在缓存更新、数据库更新、其他实例更新之间做一致性协调(如通过消息通知清缓存、短 TTL、版本号)。

外部缓存解决"多实例共享"的同时放大了"一致性"难度。二级缓存不适合频繁更新或强一致的数据。工程上需权衡 TTL 与业务容忍度,或用消息通知主动失效。

#
★★★

4. MyBatis 二级缓存的 flushInterval 与 Redis TTL 的协调及缓存穿透/雪崩防护

请说明 MyBatis 二级缓存的 flushInterval 与 Redis TTL 的协调,以及缓存穿透/雪崩的防护?

  • flushInterval 与 TTL 的关系
  • 缓存穿透(空值)
  • 缓存雪崩(大批量同时过期)

MyBatis 二级缓存的 flushInterval 决定缓存多久刷新一次,Redis TTL 决定缓存键的存活时间,二者需协调,通常让 Redis TTL 覆盖 flushInterval 语义,避免缓存项在逻辑刷新前就过期导致反复回源。缓存穿透指查询不存在的数据,每次打到数据库,可用缓存空值或布隆过滤器防护;缓存雪崩指大量 key 同时过期或 Redis 宕机导致请求集中打库,可通过 TTL 加随机抖动、缓存永不过期+异步更新、多级缓存、限流降级防护。协调 flushInterval 与 TTL 时还应考虑更新频率与业务容忍度。

缓存治理的核心是"回源控制"。穿透/雪崩/击穿(热点 key 集中过期)是三级缓存问题,防护策略分别对应空值缓存/布隆过滤器、TTL 随机化+多级缓存、热点 key 互斥重建。理解 flushInterval 与 TTL 的配合能避免缓存生命周期错乱。

#
★★★

5. MyBatis 动态 SQL( / /)的 SQL 拼接与预编译参数化

请说明 MyBatis 动态 SQL( / /)的拼接机制与预编译参数化?

  • 动态 SQL 标签的拼接规则
  • 的批量参数
  • 预编译参数化与防注入

MyBatis 动态 SQL 通过 OGNL 表达式在解析阶段进行条件拼接: 按条件决定是否包含片段, 自动去除多余 AND/OR, 遍历集合生成批量参数(如 in 列表)。动态 SQL 生成的是 SQL 模板,其中参数值仍通过 #{} 占位符最终交给 PreparedStatement 参数化绑定,因此动态拼接不影响预编译安全性(只要值用 #{})。 的 collection 指定集合,item 指定元素,可生成占位符序列。动态 SQL 的拼接逻辑在 SQL 层面,值在参数层面,二者分离保证安全。

动态 SQL 控制"SQL 结构",参数化控制"参数值",两者结合既灵活又安全。注意 生成大量占位符时可能受数据库参数数量限制,超大 in 列表需分批。

<select id="listByIds" resultType="User">
  SELECT * FROM t_user
  <where>
    <if test="ids != null and ids.size() > 0">
      id IN
      <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
      </foreach>
    </if>
  </where>
</select>
#
★★★

6. MyBatis 在 Spring Boot 3.5+ 对 SqlSessionFactory 与 SqlSessionTemplate 的整合细

请说明 MyBatis 在 Spring Boot 3.5+ 中对 SqlSessionFactory 与 SqlSessionTemplate 的整合?

  • SqlSessionFactoryBean 的自动配置
  • SqlSessionTemplate 的线程安全
  • 与 Spring 事务的整合

在 Spring Boot 3.5+ 中,mybatis-spring-boot-starter 自动配置 SqlSessionFactory(通过 SqlSessionFactoryBean 解析数据源、Mapper XML、类型处理器、插件等)与 SqlSessionTemplate。SqlSessionTemplate 是线程安全的 SqlSession 代理,每次方法调用在需要时获取一个 SqlSession(默认绑定到当前事务),执行完返回并归还连接,从而避免手动管理 SqlSession 的线程不安全问题。SqlSessionTemplate 与 Spring 事务管理器集成,事务内复用同一个 SqlSession 与连接,保证事务边界一致。Spring Boot 3.5+ 还支持懒加载、自动扫描 Mapper 等增强。

SqlSessionTemplate 是 MyBatis 与 Spring 集成的核心,它把 SqlSession 的生命周期交给 Spring 管理,提供线程安全与事务绑定。理解其"事务内单例复用"是掌握集成原理的关键。

#
★★★

7. MyBatis 的 MappedStatement 缓存机制

请说明 MyBatis 中 MappedStatement 的缓存机制?

  • MappedStatement 的解析与缓存
  • 命名空间与 id 唯一性
  • 对 SQL 解析开销的影响

MappedStatement 是 MyBatis 对一条 SQL 映射的完整描述(SQL、参数映射、结果映射、StatementType、缓存等),在应用启动时从 XML 或注解解析并缓存在 Configuration 中,以 namespace + id 唯一标识。MappedStatement 缓存使 SQL 模板在启动时解析一次,运行时复用,避免每次执行都重新解析,是性能优化的关键。对 MappedStatement 的修改(如通过拦截器动态改写 SQL)需保证线程安全与一致性。

MappedStatement 是"SQL 元数据"的缓存,一次解析、多次执行。理解它有助于掌握 MyBatis 的初始化流程与拦截器对 SQL 的改写时机。

#
★★★

8. MyBatis 的 StatementHandler 与预编译

请说明 MyBatis 的 StatementHandler 及其在预编译中的作用?

  • StatementHandler 的角色
  • PreparedStatement 的创建与参数绑定
  • 与 Executor 的关系

StatementHandler 是 MyBatis 中负责 SQL 语句处理的核心组件,它根据 MappedStatement 创建 Statement(PreparedStatement),绑定参数(通过 ParameterHandler)、执行 SQL、并处理结果集(通过 ResultSetHandler)。在预编译场景下,StatementHandler 创建 PreparedStatement,将 SQL 中的占位符交给数据库预编译,再由 ParameterHandler 逐参数设置值。StatementHandler 有 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 三种实现,对应普通/预编译/存储过程。Executor 是更上层执行器,调用 StatementHandler 完成实际数据库操作。

StatementHandler 是插件(拦截器)常拦截的四大对象之一,通过拦截其 prepare/parameterize 方法可改写 SQL。理解预编译发生于 StatementHandler 创建 PreparedStatement 阶段,是防注入的基础。

#
★★★

9. MyBatis 的一级缓存与二级缓存(PerpetualCache)

请说明 MyBatis 一级缓存与二级缓存的实现(PerpetualCache)?

  • PerpetualCache 的实现
  • 缓存装饰器(LRU、FIFO)
  • 一级/二级缓存的 cacheKey

MyBatis 的缓存默认实现是 PerpetualCache,它是一个基于 HashMap 的简单缓存,提供 get/put/remove/clear。一级缓存直接使用 SqlSession 内的本地缓存(PerpetualCache),以 MappedStatement + 参数等组成的 cacheKey 为键;二级缓存亦基于 PerpetualCache,但可通过装饰器(Cache 接口的组合)叠加 LRU、FIFO、软引用、定时刷新等策略,并接入第三方缓存(Redis)。cacheKey 由 SQL、参数、分页等构成,保证相同查询命中同一缓存项。二级缓存的命中范围在 namespace 内,刷新策略由 flushCache 与缓存配置决定。

PerpetualCache 是基础 Map 缓存,装饰器模式扩展了缓存策略。理解缓存 key 与装饰器组合,可定制缓存行为。一级缓存事务内有效,二级缓存跨会话共享。

#
★★★

10. 自定义插件实战,分页、字段加解密、慢查询统计应选择哪个拦截点(Executor/StatementHandler/ParameterHandler/ResultSetHandler)及执行顺序

请说明自定义 MyBatis 插件(分页、字段加解密、慢查询统计)应选择哪个拦截点及执行顺序?

  • 四大拦截对象(Executor/StatementHandler/ParameterHandler/ResultSetHandler)
  • 分页拦截 Executor 或 StatementHandler
  • 加解密拦截 ParameterHandler/ResultSetHandler

MyBatis 插件可拦截四大对象:Executor(执行器,query/update/commit)、StatementHandler(SQL 语句处理,prepare/parameterize)、ParameterHandler(参数设置,setParameters)、ResultSetHandler(结果集处理,handleResultSets)。分页通常拦截 Executor.query 或 StatementHandler.prepare,在 SQL 前改写加入分页方言;字段加解密拦截 ParameterHandler 在写入时加密参数、拦截 ResultSetHandler 在读取时解密结果;慢查询统计拦截 Executor.query/update 记录耗时。执行顺序遵循插件责任链:@Intercepts 注解声明并组装,插件通过 JDK 动态代理包装目标对象,多个插件按配置顺序层层包装,执行时按包装顺序(后配置的先执行最外层)调用。

选择拦截点要贴合"在哪个阶段干预"。SQL 改写选 StatementHandler.prepare,参数处理选 ParameterHandler,结果处理选 ResultSetHandler,整体执行统计选 Executor。理解插件责任链与执行顺序是编写稳定插件的关键。

#
★★★

11. Mapper 接口与 XML 映射的协作

请说明 MyBatis Mapper 接口与 XML 映射文件的协作机制?

  • namespace 与接口全限定名关联
  • SQL 语句 id 与接口方法对应
  • Mapper 的动态代理

MyBatis Mapper 接口与 XML 映射通过 namespace 与 id 关联:XML 的 namespace 必须等于接口全限定名,SQL 语句的 id 必须等于接口方法名,参数类型与返回类型对应。MyBatis 通过 MapperProxy 动态代理生成接口实现,方法调用时根据方法名从 Configuration 中找到对应 MappedStatement 执行。接口方法的参数通过 @Param 或默认命名映射到 SQL 中的参数。接口与 XML 可共存,也可用注解替代 XML,但复杂 SQL 推荐 XML。

namespace=接口全名、id=方法名是 Mapper 绑定契约。动态代理把接口调用桥接到 SQL 执行,简化了开发。理解命名规则可避免"Invalid bound statement"错误。

public interface UserMapper {
    User selectById(@Param("id") Long id);
}
<mapper namespace="com.example.UserMapper">
  <select id="selectById" resultType="User">
    SELECT * FROM t_user WHERE id = #{id}
  </select>
</mapper>
#
★★★

12. MyBatis 插件的 @Intercepts/@Signature 注解与多个插件的执行顺序(责任链模式)

请说明 MyBatis 插件的 @Intercepts/@Signature 注解与多个插件的执行顺序(责任链模式)?

  • @Intercepts/@Signature 声明拦截目标
  • 动态代理与责任链
  • 多个插件的执行顺序

@Intercepts 标注一个类为 MyBatis 插件,@Signature 声明拦截的接口类型、方法名与参数类型,限定插件拦截的具体方法。MyBatis 通过 JDK 动态代理为目标对象(Executor/StatementHandler 等)创建代理,插件按配置顺序层层包装,形成责任链。多个插件时执行顺序取决于配置顺序:先配置的插件被包装在内层,后配置的包装在外层,因此调用时外层先执行。插件可对目标方法进行前置/后置处理,也可调用 proceed() 放行到链的下一个。

责任链模式让插件按顺序组合,每个插件可横切 SQL 执行链路。理解 @Signature 的精确匹配与责任链顺序,能避免插件误拦截或顺序导致的行为差异。

#
★★

13. MyBatis 3.5.x 的 Optional 返回支持

请说明 MyBatis 3.5.x 对 Optional 返回类型的支持?

  • Optional 作为返回值类型
  • 空结果的语义
  • 使用注意

MyBatis 3.5.x 支持将 Mapper 方法返回值声明为 Optional,当查询无结果时返回 Optional.empty(),有结果时返回 Optional.of(value),避免返回 null 引发的空指针。该特性在解析返回值类型时识别 Optional 泛型,将实际结果包装。适用于单条查询(selectOne)场景。注意 Optional 只用于单结果,多结果场景仍用 List。

Optional 返回提升代码可读性,显式表达"可能为空"。理解其包装机制有助于编写安全的空值处理逻辑。

#
★★

14. MyBatis 与 Hibernate 的边界与选型

请说明 MyBatis 与 Hibernate 的边界与选型?

  • 半自动与全自动 ORM
  • MyBatis 的 SQL 可控性
  • Hibernate 的透明持久化

MyBatis 是半自动 ORM,SQL 由开发者编写、完全可控,结果映射灵活,适合复杂 SQL、性能敏感、数据库依赖强的场景;Hibernate 是全自动 ORM,自动生成 SQL、管理实体生命周期与缓存,开发效率高,适合对象模型复杂、CRUD 为主、跨数据库可移植的场景。选型依据:团队对 SQL 的把控能力、业务 SQL 复杂度、是否需要透明持久化与缓存、可移植性要求。MyBatis 更贴近 DBA 与 SQL 调优,Hibernate 更贴近对象建模与快速开发。

两者是"SQL 控制"与"自动映射"的权衡。复杂报表与强 SQL 用 MyBatis,复杂对象关联与可移植性用 Hibernate。混合使用也常见。

#
★★

15. MyBatis 与 Spring 6 的 JdbcClient 边界

请说明 MyBatis 与 Spring 6 的 JdbcClient 的边界?

  • JdbcClient 的流式 API
  • MyBatis 的映射与动态 SQL
  • 适用场景

Spring 6 引入 JdbcClient,提供流式、链式的 JDBC 访问 API(类似 JdbcTemplate 的现代封装),支持 fluent 方式指定 SQL、参数绑定与结果映射,适合简单、轻量的 JDBC 操作。MyBatis 提供更完整的映射能力(动态 SQL、ResultMap、TypeHandler、缓存、插件),适合复杂查询与半自动 ORM 场景。边界:JdbcClient 定位轻量 JDBC 封装,MyBatis 定位完整持久层框架。数据访问简单、希望少依赖时用 JdbcClient,复杂映射与团队熟悉 MyBatis 时用 MyBatis。

JdbcClient 与 MyBatis 不是同一层次,前者是 JDBC 流式封装,后者是完整 ORM 框架。理解边界有助于在项目中合理选择,避免重复封装。

#
★★

16. MyBatis 的 @Flush 与批量刷新

请说明 MyBatis 的 @Flush 注解与批量刷新?

  • @Flush 的作用
  • 与 BATCH Executor 的关系
  • 批量刷新时机

@Flush 注解用于 Mapper 接口方法,在 SqlSession 的 BATCH 或 REUSE 模式下强制刷新(flush)缓存中的批量语句,将累积的 SQL 立即发送到数据库。在 BATCH 模式下,多条 insert/update 会暂存在 Executor 中批量执行,直到 commit 或 flush 才真正执行;@Flush 可在中途强制刷新,便于控制批量提交时机与获取生成主键。不使用 @Flush 时,批量语句在事务提交时统一执行。

@Flush 是 BATCH 模式下控制执行时机的显式手段。理解 Executor 的批量缓冲与 flush 时机,可平衡批量提升与实时性需求。

#
★★

17. MyBatis 的 @SelectProvider/@InsertProvider 动态 SQL

请说明 MyBatis 的 @SelectProvider/@InsertProvider 等注解驱动的动态 SQL?

  • Provider 注解的用途
  • 动态 SQL 构建
  • 与 XML 动态 SQL 的取舍

@SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider 允许在 Java 类方法中动态生成 SQL,方法返回 SQL 字符串或 SQLBuilder 对象,避免了在 XML 中写复杂动态 SQL。Provider 方法可根据参数动态拼接 SQL(如条件组合),适合动态性强的场景,但 SQL 字符串拼接需注意防注入(用 #{} 或参数绑定)。缺点是 SQL 与 Java 代码混在一起,可读性略差,且需注意 SQL 构建的缓存。复杂场景仍推荐 XML 动态 SQL。

Provider 注解是"以 Java 方式生成 SQL"的能力,适合动态列、动态条件。权衡在于可读性与 XML 集中管理。理解 Provider 有助于实现灵活的动态 SQL。

#
★★

18. MyBatis 的 Cursor 与流式查询

请说明 MyBatis 的 Cursor 与流式查询?

  • Cursor 的惰性读取
  • 流式查询的内存优势
  • 使用注意

MyBatis 的 Cursor 支持流式查询,通过数据库游标逐条读取结果,避免一次性加载全部数据到内存,适合大表全量扫描或大数据量导出。Cursor 是惰性的,只有遍历时才真正从数据库获取下一行,需配合 server-side 游标(如 MySQL 的 ResultSet 流式读取配置)。使用 Cursor 时需保持连接打开、在事务内遍历,通常用 try-with-resources 确保关闭,避免内存与连接泄漏。注意流式查询会长时间占用连接,不适合超长事务。

Cursor 以"每次取一批"换取内存占用降低,但占用连接更久。理解其惰性与连接占用,合理用于大数据量但内存受限的场景。

#
★★

19. MyBatis 的 Executor 类型(SIMPLE/REUSE/BATCH)

请说明 MyBatis 的 Executor 类型(SIMPLE/REUSE/BATCH)?

  • SIMPLE/REUSE/BATCH 三种 Executor
  • 各自的 Statement 复用与批处理
  • 适用场景

MyBatis 的 Executor 有三种类型:SIMPLE 每次执行创建新的 Statement,最简单;REUSE 复用已创建的 PreparedStatement(按 SQL 缓存),减少重复预编译;BATCH 批量执行 SQL,将多条 insert/update 累积后在 flush 时统一提交,适合批量写入。BATCH 模式能显著提升批量性能,但需注意返回值可能不准确(set 结果为 0 或 -2)、且需手动 flush。type 通过 的 defaultExecutorType 或 SqlSessionFactory 配置指定。三种类型对应不同的执行策略与性能取舍。

Executor 类型影响 SQL 的执行与缓存策略。BATCH 适合批量插入,REUSE 适合重复执行同 SQL,SIMPLE 最简单。理解 Executor 是掌握 MyBatis 执行链路的基础。

#
★★

20. MyBatis 的 PageHelper 插件在分页查询中的物理分页与内存分页实现

请说明 PageHelper 插件在分页查询中的物理分页与内存分页实现?

  • PageHelper 的物理分页
  • 内存分页的代价
  • 分页方言适配

PageHelper 是 MyBatis 分页插件,通过拦截 Executor 的方法,在 SQL 执行前自动拼接分页 SQL(如 MySQL 的 LIMIT、Oracle 的 ROWNUM),实现物理分页,即数据库只返回当前页数据,性能好。物理分页需根据数据库方言生成对应分页语句。若不使用物理分页而把全量数据查回内存再分页,就是内存分页,数据量大时浪费内存与网络。PageHelper 会自动识别方言并生成分页 SQL,并支持 count 查询。正确使用 PageHelper 需保证分页参数在查询前设置,避免分页失效。

物理分页在数据库层完成分页,内存分页在应用层完成。PageHelper 的物理分页是推荐做法,内存分页仅适合数据量小或不便改 SQL 的场景。理解分页插件拦截点可避免分页失效。

#
★★

21. MyBatis 的 ParameterHandler 与 ResultSetHandler

请说明 MyBatis 的 ParameterHandler 与 ResultSetHandler 的作用?

  • ParameterHandler 的参数设置
  • ResultSetHandler 的结果映射
  • 作为拦截点的场景

ParameterHandler 负责将 Mapper 方法参数绑定到 PreparedStatement 的占位符,通过 TypeHandler 将 Java 参数转换为 JDBC 类型。ResultSetHandler 负责将 ResultSet 数据映射为 Java 对象(通过 ResultMap、TypeHandler、自动映射),是结果集到对象的关键环节。两者都是四大可拦截对象之二:ParameterHandler 可在参数设置时拦截(如参数加解密、字段脱敏),ResultSetHandler 可在结果映射时拦截(如结果解密、转换)。理解二者区分输入与输出侧的映射。

ParameterHandler 是"入库"侧,ResultSetHandler 是"出库"侧。字段加解密、权限脱敏等常在这两个拦截点实现。理解作用域可正确选择拦截点。

#
★★

22. MyBatis 的 ResultMap 在复杂对象关联映射(嵌套 ResultMap/Discriminator)中的设计

请说明 MyBatis 的 ResultMap 在复杂对象关联映射(嵌套 ResultMap、Discriminator)中的设计?

  • ResultMap 的定义与映射
  • 嵌套 association/collection
  • Discriminator 多态映射

ResultMap 定义数据库列到 Java 属性的映射,支持复杂对象关联。嵌套 association 映射一对一(或组合对象),collection 映射一对多(集合),可通过嵌套 ResultMap 或嵌套 select 实现。嵌套 select 会产生 N+1 问题,嵌套 ResultMap(join 查询关联)更高效。Discriminator 根据某列的值选择不同的 ResultMap 或映射,实现多态(如按 type 字段映射到不同子类实现)。ResultMap 设计需平衡映射复杂度与性能。

ResultMap 是 MyBatis 强映射能力的核心。嵌套映射解决了复杂对象图,Discriminator 解决多态。理解嵌套 select 的 N+1 与 join 的取舍,可优化关联查询。

#
★★

23. MyBatis 的 RowBounds 与分页

请说明 MyBatis 的 RowBounds 与分页?

  • RowBounds 的偏移与限制
  • 内存分页的局限
  • 与物理分页的区别

RowBounds 是 MyBatis 内置的分页参数,由 offset 和 limit 组成,作为方法参数传入。但 RowBounds 主要实现内存分页(逻辑分页),即先查询全部结果再在内存中截取 offset 到 limit 的数据,适合数据量小的场景;对于大数据量,内存分页会加载全量数据,浪费内存与网络,应使用物理分页(如 PageHelper 或手写 SQL 的 LIMIT)。RowBounds 也可通过自定义插件结合数据库方言实现物理分页。

RowBounds 是基础分页机制,但默认内存分页在数据量大时性能差。理解逻辑分页与物理分页的差异,是分页性能优化的关键。

#
★★

24. MyBatis 的 SQL 提取器(SqlSource)

请说明 MyBatis 的 SqlSource 与 SQL 提取机制?

  • SqlSource 的作用
  • DynamicSqlSource 与 StaticSqlSource
  • SQL 解析与 BoundSql

SqlSource 是 MyBatis 中负责根据 MappedStatement 与参数生成 BoundSql 的组件。StaticSqlSource 用于无动态 SQL 的静态 SQL,直接返回固定 SQL;DynamicSqlSource 用于含动态 SQL( / 等)的语句,每次执行时根据参数动态解析拼接成 BoundSql。BoundSql 包含最终的 SQL 文本、参数映射与额外参数。SqlSource 的解析发生在每次执行时,将动态 SQL 引擎与参数结合生成可执行的 SQL。理解 SqlSource 有助于掌握 SQL 生成流程。

SqlSource 生成 BoundSql,是"SQL 模板 + 参数"到"可执行 SQL"的桥梁。动态 SQL 每次解析,静态 SQL 可缓存。理解层次有助于插件拦截 SQL 改写。

#
★★

25. MyBatis 的 ScriptRunner 与脚本执行

请说明 MyBatis 的 ScriptRunner 与脚本执行?

  • ScriptRunner 的作用
  • 执行 SQL 脚本
  • 常见场景

ScriptRunner 是 MyBatis 提供的工具类,用于执行 SQL 脚本文件(多条 SQL 语句),常用于数据库初始化、测试数据准备、DBA 脚本执行。它从 JDBC 连接读取脚本并逐条执行,支持分号分隔、日志输出、错误处理等配置。常见于 mybatis 的 DatabaseIdProvider 初始化、测试环境建表脚本等场景。ScriptRunner 直接使用 JDBC 执行,不经过 Mapper 映射。

ScriptRunner 是"脚本执行器",与 Mapper 的 SQL 执行是两条路径。理解其用途可方便地在测试或初始化阶段执行 SQL 脚本。

#
★★

26. MyBatis 的 TypeHandler 与自定义类型映射

请说明 MyBatis 的 TypeHandler 与自定义类型映射?

  • TypeHandler 的作用
  • 自定义 TypeHandler
  • 全局注册与字段级引用

TypeHandler 负责 Java 类型与 JDBC 类型之间的转换,MyBatis 自带大量内置 TypeHandler(基本类型、日期、枚举等)。自定义 TypeHandler 实现 TypeHandler 接口(setParameter/getResult),用于特殊类型转换,如 JSON 字符串与对象互转、加密字段、自定义枚举。可通过 全局注册或 @MappedTypes/@MappedJdbcTypes 声明,也可在 resultMap 的 typeHandler 属性或 @Results 中字段级引用。自定义 TypeHandler 需注意线程安全(无状态)。

TypeHandler 是 MyBatis 类型映射的扩展点。自定义 handler 将复杂类型转换收敛到一处,复用性好。理解其与 ParameterHandler/ResultSetHandler 的结合,能实现灵活的字段级转换。

@MappedTypes(List.class)
public class JsonListTypeHandler extends BaseTypeHandler<List<String>> {
    // setNonNullParameter / getNullableResult 实现 JSON 互转
}
#
★★

27. MyBatis 的 XML 与注解的取舍

请说明 MyBatis 的 XML 映射与注解映射的取舍?

  • XML 的可维护性与动态 SQL
  • 注解的简洁性
  • 复杂 SQL 的推荐

MyBatis 支持 XML 与注解两种映射方式。XML 映射支持完整动态 SQL( / /)、ResultMap、跨语句复用、清晰的 SQL 与 Java 分离,可维护性好,适合复杂 SQL;注解映射(@Select/@Insert/@Update/@Delete/@Results)简洁、与代码同处,适合简单 CRUD,但复杂动态 SQL 和复杂结果映射在注解中表达受限(需 @SelectProvider 或内嵌脚本)。工程上复杂 SQL 推荐 XML,简单 SQL 可用注解,二者可混用。

XML 与注解是"集中管理"与"就近编写"的取舍。XML 适合复杂、可维护性要求高的 SQL;注解适合快速、简单场景。理解边界可避免在注解中写难以维护的动态 SQL。

#
★★

28. MyBatis 的 association/collection 的 N+1 解决

请说明 MyBatis 的 association/collection 的 N+1 问题及解决?

  • N+1 的成因(嵌套 select)
  • 嵌套 ResultMap 的 join 查询
  • 其他优化手段

MyBatis 中 association/collection 使用嵌套 select(select 属性指定子查询)时,遍历主结果集会对每个关联再执行一次子查询,产生 N+1 问题。解决方式:使用嵌套结果映射(嵌套 ResultMap),通过 join 查询一次把关联数据带出,避免循环查询;或使用 fetchType 与懒加载配合;或整体改用 join 查询。N+1 在数据量大时性能极差,应优先用 join + 嵌套 ResultMap 优化。

嵌套 select 便利但隐藏 N+1 代价,嵌套 ResultMap 以 join 换取单次查询。理解二者取舍是优化关联查询的关键。

#
★★

29. MyBatis 的插件机制(Interceptor)与责任链

请说明 MyBatis 的插件机制(Interceptor)与责任链?

  • Interceptor 接口与 Invocation
  • 动态代理包装
  • 责任链与 proceed

MyBatis 插件机制通过 Interceptor 接口实现,插件实现 intercept 方法,接收 Invocation(包含目标对象、方法、参数),可调用 proceed() 放行到下一个目标。MyBatis 用 JDK 动态代理为四大对象创建代理,多个插件按配置顺序层层包装形成责任链。每个插件可对目标方法做前置/后置处理、改写参数或 SQL。插件通过 @Intercepts/@Signature 声明拦截目标,插件配置在 mybatis-config 的 中。责任链顺序由配置顺序决定。

插件机制是 MyBatis 的 AOP 扩展点,基于责任链模式。理解 proceed 与代理顺序,可编写正确、可组合的插件(分页、加解密、审计)。

#

30. MyBatis 的 discriminator 与多态映射

请说明 MyBatis 的 discriminator 与多态映射?

  • discriminator 的用途
  • 按列值选择结果映射
  • 多态场景

MyBatis 的 用于根据结果集中的某列值动态选择不同的 ResultMap 或属性映射,实现多态映射。xml 中 内嵌 指定该值对应的映射。典型场景是表中有 type 字段且子类属性不同,按 type 映射到不同子类对象。discriminator 与继承映射结合可实现对象多态。

discriminator 是"按判别列路由映射"的机制,解决单表多态。理解它可优雅处理类型字段驱动的多态结果。

#

31. MyBatis 的 resultMap 与嵌套映射

请说明 MyBatis 的 resultMap 与嵌套映射?

  • resultMap 的基础映射
  • association/collection 嵌套
  • 自动映射与手动映射

resultMap 定义查询结果列到 Java 属性的映射,包括 id 主键、result 普通字段、association 一对一/组合、collection 一对多集合。嵌套映射(嵌套 ResultMap)通过 join 一次查询关联数据,或嵌套 select 按需加载。resultMap 支持自动映射(未配置的列自动按驼峰匹配)与手动映射(显式指定)。resultMap 是 MyBatis 处理复杂对象映射的核心。

resultMap 兼顾简单映射与复杂对象图。理解嵌套 association/collection 与自动映射,可灵活控制 ORM 映射行为。

#

32. MyBatis 的多结果集(Multiple ResultSets)

请说明 MyBatis 的多结果集(Multiple ResultSets)支持?

  • 多结果集的概念
  • 存储过程/单次多查询
  • 结果映射

MyBatis 支持一次查询返回多个结果集(Multiple ResultSets),常见于存储过程返回多个结果集或一次执行多条 SELECT。通过在 resultMap 中配置多个 resultMap(按顺序对应多个结果集),或在 的 resultMap 属性中指定多个映射,MyBatis 将每个结果集映射到对应对象。使用多结果集需保持结果集顺序与映射顺序一致。该能力扩展了复杂查询的返回形态。

多结果集适合存储过程等一次返回多个结果集的场景。理解其映射顺序约定,可正确处理多结果集返回。

#

33. MyBatis 的延迟加载与 Javassist/CGLIB

请说明 MyBatis 的延迟加载与 Javassist/CGLIB 代理?

  • 延迟加载的配置
  • 代理机制(Javassist/CGLIB)
  • 懒加载的触发与限制

MyBatis 支持延迟加载(lazy loading),通过 proxyFactory 配置(JavassistProxyFactory 或 CGLIBProxyFactory)生成关联对象的代理,访问关联属性时才真正执行查询加载数据。延迟加载减少不必要的关联查询,但需注意:懒加载在 Session 关闭后访问会抛异常(在一定配置下),且嵌套懒加载可能产生 N+1。MyBatis 默认最新版本代理机制已调整,可通过配置 lazyLoadingEnabled 与 aggressiveLazyLoading 控制。代理由 Javassist/CGLIB 动态生成子类实现。

延迟加载是"按需加载"策略,用代理拦截访问。理解其代理机制与 Session 依赖,可避免懒加载异常与性能问题。

#

34. MyBatis 的架构(SqlSessionFactory/SqlSession/Executor)

请说明 MyBatis 的架构(SqlSessionFactory/SqlSession/Executor)?

  • SqlSessionFactory 的创建
  • SqlSession 的职责
  • Executor 的执行链路

MyBatis 架构分为三层:SqlSessionFactory 由 SqlSessionFactoryBuilder 根据 Configuration 创建,是重量级单例,负责产生 SqlSession;SqlSession 是执行 SQL 的接口,封装了 Executor、StatementHandler 等,提供 select/insert/update/delete 及事务管理;Executor 是 SqlSession 内部的执行器,负责缓存、Statement 复用、批处理与实际的 SQL 执行。SqlSession 线程不安全,每个操作使用一个 SqlSession(或由 SqlSessionTemplate 管理)。整个链路:SqlSession → Executor → StatementHandler → Statement → ResultSetHandler。

理解 MyBatis 分层架构(Factory 创建、Session 执行、Executor 执行链路)是掌握框架原理与扩展点的基础。SqlSessionFactory 单例、SqlSession 轻量、Executor 是执行与缓存核心。