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

MyBatis 与 MyBatis-Plus 面试题汇总——从原理到实战

MyBatis 是国内 Java 项目中最主流的 ORM 框架,MyBatis-Plus 是它的增强工具。面试中围绕它们的底层原理、#{} 和 ${} 区别、分页原理、缓存机制等问得非常多。这篇一次说清楚。

一、MyBatis 核心原理

1. MyBatis 的工作流程

配置文件(mybatis-config.xml) ↓ SqlSessionFactoryBuilder.build() ↓ SqlSessionFactory(解析配置,构建会话工厂) ↓ SqlSession.openSession() ↓ 通过动态代理生成 Mapper 接口的实现类 ↓ 执行 Mapper 中的 SQL 语句 ↓ 返回结果

关键:Mapper 接口为什么不用写实现类?

MyBatis 用 JDK 动态代理为每个 Mapper 接口生成代理对象,代理对象根据namespace + 方法名找到对应的 SQL 并执行。

2. #{} 和 ${} 的区别

这是 MyBatis 最高频的面试题,没有之一。

对比#{}${}
处理方式预编译,替换为?直接字符串拼接
SQL 注入✅ 安全,参数值不走 SQL 编译有注入风险
场景传参(insert、update、where 条件)表名、列名动态传入(少用)
性能高(可复用预编译 SQL)低(每次重新编译)
<!-- #{} 安全写法 --><selectid="getUser"resultType="User">SELECT * FROM user WHERE id = #{id}</select><!-- 实际执行:SELECT * FROM user WHERE id = ? --><!-- ${} 危险写法 --><selectid="getUser"resultType="User">SELECT * FROM user WHERE id = ${id}</select><!-- 传入 1 OR 1=1 → 全表数据被查出 -->

结论:能用#{}的地方绝不用${}。只有动态表名、动态列名这种不得不用的场景才用${},并且要做好参数校验。

3. MyBatis 的一级缓存和二级缓存

一级缓存(默认开启):

同一个 SqlSession 中,两次相同的查询会走缓存,不会重复查数据库。 SqlSession 关闭或执行了 insert/update/delete 后缓存失效。

二级缓存(需手动开启):

同一个 SqlSessionFactory 下,多个 SqlSession 共享缓存。 适合:查询多、修改少、并发要求不高的场景。 不适合:对数据实时性要求高的场景。
<!-- 开启二级缓存 --><mappernamespace="com.zhang.mapper.UserMapper"><cacheeviction="LRU"flushInterval="60000"size="512"/></mapper>

面试常问:MyBatis 的缓存机制了解吗?一级缓存和二级缓存的区别?

二、MyBatis-Plus 面试题

1. MyBatis-Plus 和 MyBatis 的区别

MyBatisMyBatis-Plus
基础 CRUD手写 SQL自动提供,不用写
分页手写 Limit分页插件,一行代码搞定
条件查询手写动态 SQLLambdaQueryWrapper 链式调用
代码量减少 50%+
灵活度高,完全控制 SQL复杂 SQL 还是要手写

一句话总结:MyBatis-Plus 是 MyBatis 的增强工具,不为零改动——你在 MyBatis 里写复杂 SQL 的地方,MP 一样支持。

2. MyBatis-Plus 的分页原理

// 使用Page<User>page=newPage<>(1,10);userMapper.selectPage(page,null);// 自动生成 SELECT * FROM user LIMIT 0, 10// 还会自动执行 SELECT COUNT(*) FROM user 查总条数

原理:通过PaginationInnerInterceptor拦截器,在执行 SQL 前自动拼接LIMITCOUNT

注意:不配置分页拦截器,Page 对象传进去也不会生效——这是面试常挖的坑。

3. 乐观锁插件

@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());returninterceptor;}
@VersionprivateIntegerversion;

原理:更新时SET version = version + 1 WHERE version = 旧值。影响行数为 0 说明数据被修改过,需要重试。

4. 逻辑删除

@TableLogicprivateIntegerisDeleted;

原理:调用deleteById时实际执行UPDATE SET is_deleted = 1 WHERE id = ?,查询时自动拼接is_deleted = 0

5. 自动填充

@TableField(fill=FieldFill.INSERT)privateLocalDateTimecreateTime;@TableField(fill=FieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;

配合MetaObjectHandler实现 createTime 和 updateTime 自动填充,不用手动 set。

三、XML 映射文件高频问题

1. resultType 和 resultMap 的区别

<!-- resultType:列名和属性名一致时用,简单 --><selectid="getUser"resultType="com.zhang.User">SELECT id, name, email FROM user WHERE id = #{id}</select><!-- resultMap:列名和属性名不一致、有复杂关联时用 --><resultMapid="UserMap"type="User"><idcolumn="id"property="id"/><resultcolumn="user_name"property="name"/><associationproperty="dept"javaType="Dept"><idcolumn="dept_id"property="id"/><resultcolumn="dept_name"property="name"/></association></resultMap>

2. 批量插入怎么优化

<!-- 最慢:逐条插入 -->INSERT INTO user (name, email) VALUES (#{name}, #{email})<!-- 最快:批量插入 -->INSERT INTO user (name, email) VALUES<foreachcollection="list"item="item"separator=",">(#{item.name}, #{item.email})</foreach>

但要注意:MySQL 对单条 INSERT 的 VALUES 数量有限制(默认 2000 条以内),数据量大时要分批。

<!-- Service 层分批 -->userService.saveBatch(userList, 1000); // MyBatis-Plus 自带,每批 1000 条

3. 用 distinct 还是 group by 去重

<!-- 单字段去重 -->SELECT DISTINCT name FROM user<!-- 多字段分组统计 -->SELECT name, COUNT(*) AS cnt FROM user GROUP BY name HAVING cnt > 1

DISTINCT 适合简单的去重,GROUP BY 适合需要统计的场景。

四、实战场景题

场景 1:分页查询用户列表,支持姓名模糊搜索和按创建时间排序

<selectid="queryUserPage"resultType="User">SELECT * FROM user<where><iftest="name != null and name != ''">name LIKE CONCAT('%', #{name}, '%')</if></where>ORDER BY create_time DESC</select>
// Service 层Page<User>page=newPage<>(pageNum,pageSize);LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(name),User::getName,name);wrapper.orderByDesc(User::getCreateTime);returnuserMapper.selectPage(page,wrapper);

场景 2:涉及多表的关联查询

<selectid="getOrderDetail"resultMap="OrderDetailMap">SELECT o.id AS order_id, o.order_no, p.id AS product_id, p.product_name, p.price FROM seckill_order o LEFT JOIN seckill_product p ON o.product_id = p.id WHERE o.id = #{id}</select>

MyBatis-Plus 不擅长多表关联查询,复杂关联还是写 XML 更清晰。

场景 3:插入后需要返回主键

<insertid="insertUser"useGeneratedKeys="true"keyProperty="id">INSERT INTO user (name, email) VALUES (#{name}, #{email})</insert>
Useruser=newUser();user.setName("张三");userMapper.insertUser(user);System.out.println("自增主键: "+user.getId());// 插入后自动回填

MP 的save方法默认返回主键,不需要额外配置。

五、MyBatis 与 JPA 对比(面试拓展)

对比MyBatisJPA/Hibernate
上手难度中等,需要写 SQL简单,不用写 SQL
复杂 SQL✅ 完全控制❌ 复杂关联难搞
自动建表✅ 自动建
性能优化✅ 亲手写 SQL,好优化❌ 自动生成 SQL 可能不好
国内主流✅ 绝大多数企业在用较少

选型建议:国内企业主流是 MyBatis/MyBatis-Plus,面试也主要问 MyBatis。JPA 在外企和部分新项目中有用,但不是重点。


💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

http://www.jsqmd.com/news/1090704/

相关文章:

  • 3DMax新手避坑指南:模型导入、选择与显示的实战解析
  • 5个理由选择FreeShip Plus:零成本专业船舶设计完全指南
  • 应急电源深度实测:锂电池 vs 镁金属空气电池,6个核心维度选型对比
  • NifSkope深度解析:游戏文件编辑架构与扩展开发最佳实践
  • shader开发工具
  • ComfyUI BrushNet图像修复工作流终极配置指南:5个常见错误与解决方案
  • 告别“more than one device/emulator”困扰:精准定位与高效调试指南
  • ComfyUI-Impact-Pack终极指南:5个技巧让AI图像细节清晰如镜
  • DP159RGZ评估模块硬件设计与信号完整性调试实战解析
  • 鸿蒙 ArkTS 实战:Paper Reader 从状态建模到交互闭环完整解析
  • 从线芯排列到传输性能:深度解析超五类与六类水晶头的设计哲学与实战选择
  • 微信网页版访问受限?三分钟教你通过浏览器插件绕过限制
  • 异步爬虫 aiohttp 进阶实战——高并发采集的正确姿势
  • 鸿蒙 ArkTS 实战:Lab Record Book 从状态建模到交互闭环完整解析
  • Python 知识体系深度解析与学习指南
  • 【操作系统】经典同步问题:生产者-消费者
  • 李宏毅深度学习课程集成学习学习报告
  • AI模型能力演进与安全发布机制解析
  • 3分钟掌握HS2-HF Patch:一站式汉化去码解决方案终极指南
  • 93亿反杀800亿!Ideogram 4登顶开源之王,设计师要失业了?
  • 2026年想找靠谱的金相显微镜工厂 这些实用选购干货值得你参考
  • Android binder(RPC) 通信概念与架构
  • Gemini原生多模态:统一表示空间与跨模态因果推理
  • TVA在具身智能产业化体系的落地案例详解(4)
  • 文件上传漏洞防御实战:从原理到PHP安全实现
  • 15分钟构建专业级黑苹果配置:OpCore-Simplify的智能化解决方案
  • SN65DSI8X视频桥接芯片硬件设计:从电源管理到高速信号完整性实战
  • 为什么你的ChatGPT API账单比同行高3.2倍?——GPT-4 Turbo vs GPT-3.5 Turbo的11项成本对比实验报告
  • Dalín X 意识框架实测数据报告
  • 技术桥接中的抽象分离与实现独立