尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

MyBatis 与 MyBatis-Plus 面试题汇总——从原理到实战
📅 发布时间:2026/6/29 17:13:33

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 前自动拼接LIMIT和COUNT。

注意:不配置分页拦截器,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/爬虫 实战干货,不让你白来。

相关新闻

  • 大模型项目进入生产后,真正难管的不是模型:一套 API 接入与向量检索运行手册
  • DP159RGZ评估模块硬件设计与信号完整性调试实战解析
  • 李宏毅深度学习课程集成学习学习报告

最新新闻

  • 春考:把握升学新通道,走出更适合自己的成长路径
  • AI Agent 的四大组成部分详解
  • 芝麻粒TK版:让蚂蚁森林能量管理变得轻松简单的智能助手
  • 基于Web的实验室智能排课系统的设计与实现
  • DLSS Swapper完整指南:三分钟学会智能游戏性能优化
  • 全降式气流净化架构大型工业喷漆房软硬件系统拆解——越华环保集团设备技术分析

日新闻

  • ENVI5.3.1实战:基于Landsat 8影像的区域无缝镶嵌与精准裁剪
  • 3步完成HS2-HF Patch安装:新手快速打造完美HoneySelect2体验
  • 微信好友检测终极指南:3分钟发现谁已悄悄删除你

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号