《Java 100 天进阶之路》第90篇:MyBatis面试高频真题(2026版)

📌 专栏连载《2026全新 | Java 100 天进阶之路:从零基础到上岗就业,108篇完整学习地图》

⬅️ 上一篇:第89篇:MySQL面试压轴题 |

➡️ 下一篇:第91篇:Redis核心数据结构


文章目录


一、MyBatis SQL完整执行流程链路图

💡 重点:面试官问执行流程时,直接画出这张图,秒杀80%只会背文字的竞争者!
#mermaid-svg-MzpWqJMCQRo3gghe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MzpWqJMCQRo3gghe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MzpWqJMCQRo3gghe .error-icon{fill:#552222;}#mermaid-svg-MzpWqJMCQRo3gghe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MzpWqJMCQRo3gghe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MzpWqJMCQRo3gghe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MzpWqJMCQRo3gghe .marker.cross{stroke:#333333;}#mermaid-svg-MzpWqJMCQRo3gghe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MzpWqJMCQRo3gghe p{margin:0;}#mermaid-svg-MzpWqJMCQRo3gghe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MzpWqJMCQRo3gghe .cluster-label text{fill:#333;}#mermaid-svg-MzpWqJMCQRo3gghe .cluster-label span{color:#333;}#mermaid-svg-MzpWqJMCQRo3gghe .cluster-label span p{background-color:transparent;}#mermaid-svg-MzpWqJMCQRo3gghe .label text,#mermaid-svg-MzpWqJMCQRo3gghe span{fill:#333;color:#333;}#mermaid-svg-MzpWqJMCQRo3gghe .node rect,#mermaid-svg-MzpWqJMCQRo3gghe .node circle,#mermaid-svg-MzpWqJMCQRo3gghe .node ellipse,#mermaid-svg-MzpWqJMCQRo3gghe .node polygon,#mermaid-svg-MzpWqJMCQRo3gghe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MzpWqJMCQRo3gghe .rough-node .label text,#mermaid-svg-MzpWqJMCQRo3gghe .node .label text,#mermaid-svg-MzpWqJMCQRo3gghe .image-shape .label,#mermaid-svg-MzpWqJMCQRo3gghe .icon-shape .label{text-anchor:middle;}#mermaid-svg-MzpWqJMCQRo3gghe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MzpWqJMCQRo3gghe .rough-node .label,#mermaid-svg-MzpWqJMCQRo3gghe .node .label,#mermaid-svg-MzpWqJMCQRo3gghe .image-shape .label,#mermaid-svg-MzpWqJMCQRo3gghe .icon-shape .label{text-align:center;}#mermaid-svg-MzpWqJMCQRo3gghe .node.clickable{cursor:pointer;}#mermaid-svg-MzpWqJMCQRo3gghe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MzpWqJMCQRo3gghe .arrowheadPath{fill:#333333;}#mermaid-svg-MzpWqJMCQRo3gghe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MzpWqJMCQRo3gghe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MzpWqJMCQRo3gghe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MzpWqJMCQRo3gghe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MzpWqJMCQRo3gghe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MzpWqJMCQRo3gghe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MzpWqJMCQRo3gghe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MzpWqJMCQRo3gghe .cluster text{fill:#333;}#mermaid-svg-MzpWqJMCQRo3gghe .cluster span{color:#333;}#mermaid-svg-MzpWqJMCQRo3gghe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MzpWqJMCQRo3gghe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MzpWqJMCQRo3gghe rect.text{fill:none;stroke-width:0;}#mermaid-svg-MzpWqJMCQRo3gghe .icon-shape,#mermaid-svg-MzpWqJMCQRo3gghe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MzpWqJMCQRo3gghe .icon-shape p,#mermaid-svg-MzpWqJMCQRo3gghe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MzpWqJMCQRo3gghe .icon-shape .label rect,#mermaid-svg-MzpWqJMCQRo3gghe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MzpWqJMCQRo3gghe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MzpWqJMCQRo3gghe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MzpWqJMCQRo3gghe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 加载配置构建SqlSessionFactory
openSession获取SqlSession
getMapper生成MapperProxy代理
调用Mapper接口方法
MapperMethod分发SQL类型
Executor先查询一二级缓存
StatementHandler创建PreparedStatement
ParameterHandler填充占位符
执行JDBC查询
ResultSetHandler映射实体返回

二、基础概念篇(5道高频题)

Q1:MyBatis 是什么?它和 JDBC 有什么区别?

MyBatis 是一个半自动 ORM 框架,通过 XML 或注解配置 SQL 语句,将 Java 对象与数据库记录映射。JDBC 需要手动获取连接、创建 Statement、执行 SQL、处理结果集、关闭资源;MyBatis 封装了这些重复操作。

背诵口诀:半自动ORM写SQL,JDBC封装少写码,灵活度高好优化。

Q2:MyBatis 和 Hibernate 的区别?

对比项 MyBatis Hibernate
ORM类型 半自动 全自动
SQL控制 手写SQL,灵活度高 自动生成,适合简单CRUD
学习成本 较低 陡峭
N+1问题 通过<association>/<collection>规避 常见,需额外优化

面试官追加 :Hibernate 的 N+1 查询问题怎么解决?(答:通过 <association><collection> 标签优化关联查询,或使用 @BatchSize

Q3:MyBatis 的核心组件有哪些?

SqlSessionFactoryBuilderSqlSessionFactorySqlSessionExecutorMappedStatementMapper

小结Configuration 存储所有配置,是 MyBatis 的"大脑"。

Q4:MyBatis 有哪些优点和缺点?

优点:SQL 与代码分离,灵活性高;支持动态 SQL;方便进行 SQL 优化。

缺点:需要手写 SQL;XML 配置较多;对数据库移植性有一定影响。

Q5:什么是 ORM?MyBatis 属于哪种 ORM?

ORM 全称 Object Relational Mapping(对象关系映射)。MyBatis 属于半自动 ORM,因为 SQL 需要开发者自己写。


三、核心SQL配置篇(8道真题)

Q6:#{}${} 的区别是什么?

对比项 #{} ${}
底层处理 预编译占位符 → ? 纯字符串替换
SQL注入 ✅ 防止 ❌ 存在风险
适用场景 参数值传递 动态表名/列名
推荐度 优先使用 谨慎使用,需白名单

⚠️ 重点 :能用 #{} 的地方绝不用 ${};表名/列名动态场景只能用 ${},但需白名单校验!

Q7:MyBatis 如何防止 SQL 注入?

核心是 #{} 预编译。SQL 模板先发到数据库预编译,参数值通过占位符独立传递,不被解析为 SQL 逻辑。

面试官追加 :MyBatis-Plus 的 LambdaQueryWrapper 如何防注入?(答:使用 Lambda 表达式封装字段名,避免字符串拼接)。

Q8:动态 SQL 有哪些标签?执行原理是什么?

包括 <if><choose>/<when>/<otherwise><where><set><foreach><trim><bind>。原理是根据 OGNL 表达式动态拼接 SQL。

Q9:<foreach> 标签的用法?

用于遍历集合,常见于批量插入或 IN 查询。

  • ⚠️ 重点 :使用 ExecutorType.BATCH 批量提交性能更佳,注意 MySQL max_allowed_packet 参数限制。

Q10:<bind> 标签的作用是什么?

从 OGNL 表达式中创建变量并绑定到上下文。常用于模糊查询拼接 %,避免在业务代码中硬编码。

Q11:实体类属性名和表字段名不一致怎么办?

方案一:resultMap 手动映射;方案二:开启驼峰命名转换;方案三:使用 @Results 注解。

Q12:resultTyperesultMap 的区别?

resultType 要求列名与属性名一致,自动映射;resultMap 支持自定义映射,处理复杂关联。

Q13:MyBatis 分页如何实现?

方案一:SQL 手写 LIMIT;方案二:PageHelper 分页插件(底层拦截 Executor.query() 自动拼接 LIMIT)。


四、Mapper动态代理源码篇(5题)

Q14:Mapper 接口为什么不需要实现类?

MyBatis 使用 JDK 动态代理 。运行时生成 MapperProxy 代理对象,拦截接口方法,转而执行 MappedStatement 代表的 SQL。

背诵口诀:JDK代理生成Proxy,拦截方法找SQL。

Q15:Mapper 接口方法能重载吗?

不能 。MyBatis 使用 全限定名 + 方法名 作为唯一标识,重载会导致映射冲突。

Q16:MyBatis 执行 SQL 的完整流程?

(参见文章开头完整链路图)读取配置 → 创建 SqlSession → Mapper代理调用 → Executor执行 → 参数处理 → 结果映射 → 返回。

Q17:MyBatis 的 Executor 有哪些?

执行器 特点 适用场景
SimpleExecutor 每次创建新Statement 默认,通用
ReuseExecutor 复用Statement 减少创建开销
BatchExecutor 批量执行 批量操作
CachingExecutor 装饰器包装 开启二级缓存时

面试官追加 :开启二级缓存时用什么 Executor?(答:CachingExecutor,装饰器模式包装基础执行器)

Q18:MyBatis 插件(Interceptor)的原理?

拦截四大组件(ExecutorStatementHandlerParameterHandlerResultSetHandler)的方法,通过 InterceptorChain 形成责任链


五、缓存机制篇(5题)

Q19:MyBatis 的一级缓存和二级缓存的区别?

对比项 一级缓存 二级缓存
作用范围 SqlSession 级别 Mapper(Namespace)级别
默认状态 默认开启 需手动配置
生命周期 会话关闭即销毁 跨SqlSession共享
实现方式 基于HashMap本地缓存 需配置<cache/>

Q20:一级缓存失效的场景?

  1. 不同 SqlSession;2. 执行增删改操作;3. 手动 clearCache();4. 事务提交/回滚;5. SqlSession 关闭。

Q21:二级缓存如何配置?

Mapper XML 添加 <cache/> 标签,实体类实现 Serializable

Q22:生产环境为什么建议禁用二级缓存?

⚠️ 重点 :在分布式微服务架构下,MyBatis 原生二级缓存基于本地内存,多节点部署极易导致数据不一致。生产环境标准做法:直接禁用,转而使用 Redis 等分布式缓存 !若需本地加速,推荐 Spring Cache + Caffeine

Q23:二级缓存脏读问题如何解决?

多表关联更新不会自动清空其他表的缓存。解决:关联表缓存在同一 namespace;或使用 cache-ref;或多表查询禁用二级缓存。

💬 互动思考:你们项目是否踩过 MyBatis 二级缓存脏读问题?生产是如何解决的?评论区交流方案!


六、Spring整合篇(3题)

Q24:Spring 如何整合 MyBatis?

SqlSessionFactoryBean 创建工厂;@MapperScan 扫描接口注册为 MapperFactoryBeanSqlSessionTemplate 管理生命周期。

Q25:@MapperScan 注解的原理?

@Import(MapperScannerRegistrar.class),启动时扫描指定包,将 Mapper 接口注册为 MapperFactoryBean

Q26:Spring 整合后 Mapper 注入的是什么对象?

注入的是 JDK 动态代理对象MapperProxy)。


七、MyBatis-Plus全考点(6题)

Q27:MyBatis-Plus 是什么?

MyBatis 的增强工具,只做增强不做改变。MP = MyBatis + 通用 CRUD + 条件构造 + 内置插件。

Q28:MyBatis-Plus 的核心组件有哪些?

BaseMapperIService/ServiceImplWrapper(条件构造器);Plugin(分页、乐观锁等);SqlInjector

Q29:MP 如何在不修改 MyBatis 源码的前提下实现通用 CRUD?

依靠自动配置类 MybatisPlusAutoConfiguration 扩展 MyBatis 原生机制,无侵入改造:

  1. Spring Boot 启动时自动注入 MybatisSqlSessionFactoryBean 替代原生工厂 Bean
  2. MapperRegistry 初始化阶段,通过 AbstractSqlInjector 动态生成 insertselectById 等通用方法对应的 MappedStatement
  3. 将生成好的 SQL 映射注册至 MyBatis 全局 Configuration,完全复用 MyBatis 原生执行流程,不改动底层源码

Q30:MP 分页插件如何配置?如何优化 Count SQL 陷阱?

⚠️ 重点 :复杂 LEFT JOIN 查询中,自动生成的 COUNT(*) 性能极差!最佳实践:关闭自动 count,手动编写轻量级 count SQL。

java 复制代码
// MP分页Count优化标准代码
Page<User> page = new Page<>(pageNum, pageSize);
// 关闭自动count,复杂联查必开
page.setSearchCount(false);

// 仅查询主表统计,性能提升数倍
Long total = userMapper.selectCount(wrapper);
page.setTotal(total);

List<User> list = userMapper.selectPage(page, wrapper);

Q31:MyBatis-Plus 的逻辑删除原理?

@TableLogic 标记字段,删除变为 UPDATE,查询自动过滤。底层通过 LogicSqlInjector 改写 SQL。

Q32:MyBatis-Plus 的乐观锁如何实现?

实体类添加 @Version 字段,更新时检查版本号是否匹配,匹配则更新并 +1。


八、生产环境避坑汇总表

坑点 危害星级 正确做法
SQL注入 ⭐⭐⭐⭐⭐ 能用 #{} 就别用 ${},动态表名需白名单校验
二级缓存分布式不一致 ⭐⭐⭐⭐⭐ 生产环境禁用 MyBatis 二级缓存,改用 Redis
MP分页COUNT陷阱 ⭐⭐⭐⭐⭐ 复杂查询手动设置 setSearchCount(false) + 手动count
批量插入性能差 ⭐⭐⭐⭐☆ 使用 <foreach>saveBatch,开启 rewriteBatchedStatements
Mapper重载 ⭐⭐⭐☆☆ 方法名保持唯一,避免映射冲突
逻辑删除未配置 ⭐⭐⭐⭐☆ 必须标注 @TableLogic,防止物理删除

九、课后面试真题练习

1. 分析题:某系统使用 MyBatis 二级缓存,关联表更新后其他查询读到旧数据,为什么?如何解决?

思路 :二级缓存基于 Mapper namespace,关联表更新不会清空其他表的缓存。解决:关联表缓存在同一 namespace,或使用 cache-ref,或在分布式环境下直接禁用改用 Redis。

2. 代码题 :使用 MyBatis-Plus 的 LambdaQueryWrapper 实现用户多条件分页查询(姓名模糊、年龄区间、状态精确),并处理复杂关联查询的 count 优化。

思路 :参考 Q30 代码示例,使用 Lambda 表达式封装条件,关闭自动 count 并手动设置 total

3. 源码题:简述 MyBatis 插件(Interceptor)的拦截链构建过程。

思路Configuration 持有 InterceptorChain 存储全部插件;创建 Executor/StatementHandler 等四大对象时执行 pluginAll();循环调用每个拦截器 plugin 方法生成多层代理,形成责任链。PageHelper、MP 分页插件均基于此机制。


📊 你的学习进度

  • 当前:第90篇 / 共108篇 · 进阶篇:数据库与持久层框架(第83~90篇)
  • ✅ 已完成:基础篇44篇 + 第91~96篇(Redis/MQ)+ 第83~90篇
  • 📖 正在学:第90篇
  • ⏳ 待学习:第97~108篇(微服务/物联网/AI/设计模式/面试压轴)

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

👉 下一篇文章预告

《第91篇:Redis核心数据结构(2026版)》

内容简介:Redis 五种核心数据结构(String、Hash、List、Set、ZSet)底层实现与适用场景,HyperLogLog、Bitmap 高级结构,生产环境避坑与面试高频题。

🎁 福利提醒:评论区留言"MyBatis面试"可领取《MyBatis 面试 32 题速答卡》PDF。

📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注 ,一起100天拿offer!

👉 点击关注我,更新后第一时间收到推送!