当前位置: 首页 > news >正文

MyBatis 面试连环炮:从源码原理到实战避坑,彻底拿下 Offer 通关秘籍

无论是初级工程师还是高级架构师,MyBatis 都是 Java 后端开发中绕不开的核心组件。面试官对它的考察早已不再局限于“怎么写 SQL”,而是深入到了SQL 注入防护、缓存穿透/脏读、分页插件底层原理等核心深度。

为了帮你“吊打”面试官,本文将从以下 5 个维度为你深度拆解:

  1. #{}${}的生死抉择—— 防止 SQL 注入的底线
  2. 动态 SQL 的优雅与陷阱—— 什么时候该用where,什么时候必须用trim
  3. 缓存失效的真相—— 一级缓存与二级缓存的爱恨情仇
  4. 分页插件的黑魔法—— 物理分页 vs 逻辑分页的性能博弈
  5. Mapper 接口的秘密—— 为什么没有实现类也能运行?

🔍 第一回合:面试现场 ——#{}${}的区别

🎯 面试官提问

“我们在写 SQL 时,#{}${}到底有什么区别?什么时候必须用${}?”

💡 核心解析

这不仅仅是语法的区别,更是安全与性能的区别。

1. 核心对比表

对比维度#{} (预编译占位符)${} (字符串拼接)处理机制预编译 (Prepared Statement),先编译 SQL 模板,再传参数字符串替换,先拼接字符串,再编译执行

对比维度#{} (预编译占位符)${} (字符串拼接)
处理机制预编译 (Prepared Statement),先编译 SQL 模板,再传参数字符串替换,先拼接字符串,再编译执行
SQL 注入✅ 安全,参数被当作值处理❌ 极度危险,参数可能被当作 SQL 代码执行
执行性能高,数据库可缓存执行计划 (Execution Plan)低,每次 SQL 字符串都不同,无法缓存
类型转换自动进行 Java Type -> JDBC Type 转换无,直接拼接字符串
典型场景绝大多数参数传递 (WHERE 条件)动态表名、列名、ORDER BY 字段

SQL 注入✅ 安全,参数被当作值处理❌ 极度危险,参数可能被当作 SQL 代码执行执行性能高,数据库可缓存执行计划 (Execution Plan)低,每次 SQL 字符串都不同,无法缓存类型转换自动进行 Java Type -> JDBC Type 转换无,直接拼接字符串典型场景绝大多数参数传递 (WHERE 条件)动态表名、列名、ORDER BY 字段

2. 深度场景剖析:为什么有时候非得用${}

虽然#{}很安全,但在某些场景下它是“无能为力”的。

  • 场景一:动态表名如果你的业务需要根据时间分表(如user_2024,user_2025),表名是 SQL 的结构部分,预编译占位符?不允许出现在表名位置
    <!-- 必须使用 $ {} --> <select id=「findUserByTable」 resultType=「User」> SELECT * FROM $ {tableName} WHERE id = #{id} </select>
    避坑指南:如果必须用${},请务必在 Java 代码层做白名单校验,绝对不能直接拼接用户输入!
  • 场景二:动态排序 (ORDER BY)
    <!-- 动态按不同列排序 --> <select id=「findUser」 resultType=「User」> SELECT * FROM user ORDER BY $ {sortColumn} $ {sortOrder} </select>

🪄 第二回合:动态 SQL —— 让代码更聪明

🧩 核心标签实战

MyBatis 的动态 SQL 是基于 OGNL 表达式实现的,它能让你的 XML 像编程语言一样灵活。

1. 解决“多余关键字”的神器:<where><set>

  • 痛点:在拼接ANDOR时,很容易出现语法错误(如WHERE AND name = 『xxx』)。
  • 方案:使用<where>标签,它会智能判断:如果内部标签没有返回任何内容,它就不生成WHERE子句;如果第一个条件带AND,它会自动去除。
<select id=「findUser」 resultType=「User」> SELECT * FROM user <!-- <where> 会自动处理第一个 and/or --> <where> <if test=「name != null and name != 『』」> AND name = #{name} </if> <if test=「age != null」> AND age = #{age} </if> </where> </select> ```` #### 2. 批量操作:`<foreach>` 这是面试中考察并发和性能的常见点。 ```xml <!-- IN 查询 --> <select id=「selectByIds」 resultType=「User」> SELECT * FROM user WHERE id IN <foreach collection=「list」 item=「id」 open=「(」 separator=「,」 close=「)」> #{id} </foreach> </select> <!-- 批量插入 --> <insert id=「batchInsert」> INSERT INTO user (name, age) VALUES <foreach collection=「list」 item=「user」 separator=「,」> (#{user.name}, #{user.age}) </foreach> </insert>

🧠 第三回合:缓存机制 —— 一级与二级缓存的博弈

⚖️ 缓存对比全景图

特性一级缓存 (Local Cache)二级缓存 (Global Cache)作用域 SqlSession 级别 Mapper (Namespace) 级别默认状态✅ 开启 (无法关闭)❌ 关闭 (需手动配置)数据共享同一个会话内共享跨 SqlSession 共享 (应用级)脏读风险无 (会话结束即销毁)有 (多表操作导致数据不一致)底层实现

特性一级缓存 (Local Cache)二级缓存 (Global Cache)
作用域SqlSession 级别Mapper (Namespace) 级别
默认状态✅ 开启 (无法关闭)❌ 关闭 (需手动配置)
数据共享同一个会话内共享跨 SqlSession 共享 (应用级)
脏读风险无 (会话结束即销毁)有 (多表操作导致数据不一致)
底层实现PerpetualCache (HashMap)PerpetualCache + 装饰器模式

PerpetualCache (HashMap)PerpetualCache + 装饰器模式

🚨 经典面试题:二级缓存的“脏读”怎么解决?

场景模拟
假设你有两个 Mapper:

  1. UserMapper:查询用户信息。
  2. UserRoleMapper:负责给用户分配角色(更新操作)。

问题
UserMapper开启了二级缓存。当UserRoleMapper修改了用户的角色后,UserMapper的缓存并没有失效!下次查询用户时,读到的还是旧的角色信息 ——这就是脏读

解决方案

  1. 方案 A (粗暴):直接禁用二级缓存(推荐在分布式环境下使用 Redis 替代)。
  2. 方案 B (优雅):使用<cache-ref>标签,让两个 Mapper 引用同一个缓存区域。
    <!-- 在 UserRoleMapper.xml 中添加 --> <cache-ref namespace=「com.demo.mapper.UserMapper」/>
    这样,当UserRoleMapper执行增删改时,会刷新UserMapper的缓存,从而保持一致性。

📄 第四回合:分页插件原理 —— 拒绝内存溢出

📊 分页方式大比拼

类型实现方式优点缺点逻辑分页 RowBounds (内存分页)简单,不依赖数据库方言极慢且危险,先查出所有数据再截取,大数据量直接 OOM 物理分页 LIMIT / ROWNUM

类型实现方式优点缺点
逻辑分页RowBounds (内存分页)简单,不依赖数据库方言极慢且危险,先查出所有数据再截取,大数据量直接 OOM
物理分页LIMIT / ROWNUM高性能,只查需要的数据需要处理不同数据库的方言 (MySQL vs Oracle)

高性能,只查需要的数据需要处理不同数据库的方言 (MySQL vs Oracle)

🛠️ PageHelper 插件是如何工作的?

PageHelper 的核心原理是利用了 MyBatis 的Interceptor (拦截器)机制。

执行流程图解

  1. 入口PageHelper.startPage(1, 10)—— 将分页参数存入ThreadLocal(保证线程安全)。
  2. 拦截:拦截Executor.query()方法。
  3. 改写:从ThreadLocal取出参数,将原 SQL 改写为带LIMIT的物理分页 SQL。
  4. 统计:自动生成并执行COUNT(*)查询,获取总记录数。
  5. 封装:将数据和总数封装成PageInfo对象返回。
避坑指南PageHelper依赖ThreadLocal,如果在startPage后执行了多个查询,后面的查询会被“污染”。最佳实践是紧跟着startPage写唯一的查询语句,或者手动调用clearPage()

🧙‍♂️ 第五回合:Mapper 映射原理 —— 无中生有的实现类

🤔 灵魂拷问

“为什么我们的 Mapper 只是一个接口,没有任何实现类,却能直接注入并调用?”

⚙️ 底层真相:JDK 动态代理

MyBatis 在启动时,会扫描所有的 Mapper 接口,并利用JDK 动态代理为它们生成代理对象(Proxy)。

调用链路

  1. 解析:解析 XML 或注解,生成MappedStatement对象,并存入Configuration
  2. 代理:当调用sqlSession.getMapper(UserMapper.class)时,生成MapperProxy
  3. 执行:调用userMapper.selectById(1)时,代理对象会根据接口全限定名 + 方法名拼接成statementId(如com.demo.mapper.UserMapper.selectById)。
  4. 映射:根据statementIdConfiguration中找到对应的 SQL 和参数,执行数据库操作。

为什么 Mapper 接口不能重载方法?
因为statementId仅由接口名 + 方法名组成,不包含参数列表。如果重载,会导致多个方法对应同一个statementId,从而引发冲突。


📝 总结与避坑指南

为了方便记忆,我为你整理了这份面试速记表

核心考点关键词避坑点参数占位符预编译 vs 字符串替换动态表名必须用 ${},但要防注入一级缓存 SqlSession 级别增删改操作会自动清空缓存二级缓存 Namespace 级别多表操作同数据时需用 <cache-ref> 解决脏读分页插件拦截器 + ThreadLocal 避免 RowBounds 导致的内存溢出动态代理 JDK Proxy

核心考点关键词避坑点
参数占位符预编译 vs 字符串替换动态表名必须用 ${},但要防注入
一级缓存SqlSession 级别增删改操作会自动清空缓存
二级缓存Namespace 级别多表操作同数据时需用 <cache-ref> 解决脏读
分页插件拦截器 + ThreadLocal避免 RowBounds 导致的内存溢出
动态代理JDK Proxy接口方法不能重载

接口方法不能重载

http://www.cnnetsun.cn/news/1929498.html

相关文章:

  • 如何在Navicat中使用逻辑模型转为物理模型_架构师必备技能
  • 深度学习基于YOLOv8的苹果叶片病害检测系统 Python + PyQt5可视化界面 苹果病害检测数据集的训练及应用
  • UVM验证平台组件通信避坑指南:从TLM端口连接到uvm_config_db的正确用法
  • 用立创EDA复刻FM5324GA充放电模块,做个雪花灯底座给娃当礼物(附完整BOM清单)
  • 有机化学中苯环上取代基的次序规则
  • 手把手教你用STM32F103C8T6和MAX30102做个心率血氧仪(附完整代码和算法解析)
  • 如何用UABEA轻松修改Unity游戏资源?新手完整指南
  • 生成式AI多语言支持实战手册(覆盖127种语系+低资源语言破局方案)
  • vSphere 6.7证书突然过期,凌晨三点我这样救活了vCenter
  • 工业肌肉:04 机器人为什么能精准到 0.01mm?运动控制三参数告诉你
  • 告别环境配置噩梦:用PothosSDR 2020.01.26一站式搞定Windows下的USRP开发环境
  • 步进电机选型翻车实录:从振动、丢步到扭矩不足,我的三个项目踩坑总结与避坑指南
  • PyTorch加载.pth文件报错?手把手教你三种离线下载预训练模型的方法(附国内网络解决方案)
  • LangChain API 参考文档简介
  • FanControl终极指南:5分钟掌握Windows风扇精准控制
  • 【学习笔记】Spring IOC为何重要?
  • 八大网盘直链下载助手:免费开源工具实现下载速度10倍提升
  • Linux C编程基础知识(文件目录操作)
  • FaceFusion使用技巧:如何设置参数获得最佳换脸效果?
  • 破局高速与疲劳测量难题:DIC技术的全场动态视界
  • Nucleus Co-Op深度解析:单机游戏分屏多人协作的完整技术指南
  • 大模型应用开发入门指南:小白程序员轻松进阶,收藏必备!
  • 从理论到实践:基于Python与RealSense的JAKA机械臂手眼标定全流程解析
  • 三步掌握百度网盘秒传脚本:永久分享文件的终极解决方案
  • STK 12.2 与 MATLAB R2021a 联调避坑全记录:从环境配置到第一个Python脚本跑通
  • 激光SLAM实战解析——从特征点提取到匹配优化全流程
  • SITS2026图谱紧急预警:3类正在被淘汰的AI应用模式,及2025年前必须升级的4项架构能力
  • csp信奥赛C++高频考点专项训练之贪心算法 --【线性扫描贪心】:分糖果
  • Java组件安全实战:FastJson漏洞利用与WAF绕过技巧
  • BibTeX参考文献实战指南:从入门到精通各类文献格式