资讯中心

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

📅 2026/6/29 17:00:00
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低每次重新编译!-- #{} 安全写法 --selectidgetUserresultTypeUserSELECT * FROM user WHERE id #{id}/select!-- 实际执行SELECT * FROM user WHERE id ? --!-- ${} 危险写法 --selectidgetUserresultTypeUserSELECT * FROM user WHERE id ${id}/select!-- 传入 1 OR 11 → 全表数据被查出 --结论能用#{}的地方绝不用${}。只有动态表名、动态列名这种不得不用的场景才用${}并且要做好参数校验。3. MyBatis 的一级缓存和二级缓存一级缓存默认开启同一个 SqlSession 中两次相同的查询会走缓存不会重复查数据库。 SqlSession 关闭或执行了 insert/update/delete 后缓存失效。二级缓存需手动开启同一个 SqlSessionFactory 下多个 SqlSession 共享缓存。 适合查询多、修改少、并发要求不高的场景。 不适合对数据实时性要求高的场景。!-- 开启二级缓存 --mappernamespacecom.zhang.mapper.UserMappercacheevictionLRUflushInterval60000size512//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 的分页原理// 使用PageUserpagenewPage(1,10);userMapper.selectPage(page,null);// 自动生成 SELECT * FROM user LIMIT 0, 10// 还会自动执行 SELECT COUNT(*) FROM user 查总条数原理通过PaginationInnerInterceptor拦截器在执行 SQL 前自动拼接LIMIT和COUNT。注意不配置分页拦截器Page 对象传进去也不会生效——这是面试常挖的坑。3. 乐观锁插件BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();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(fillFieldFill.INSERT)privateLocalDateTimecreateTime;TableField(fillFieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;配合MetaObjectHandler实现 createTime 和 updateTime 自动填充不用手动 set。三、XML 映射文件高频问题1. resultType 和 resultMap 的区别!-- resultType列名和属性名一致时用简单 --selectidgetUserresultTypecom.zhang.UserSELECT id, name, email FROM user WHERE id #{id}/select!-- resultMap列名和属性名不一致、有复杂关联时用 --resultMapidUserMaptypeUseridcolumnidpropertyid/resultcolumnuser_namepropertyname/associationpropertydeptjavaTypeDeptidcolumndept_idpropertyid/resultcolumndept_namepropertyname//association/resultMap2. 批量插入怎么优化!-- 最慢逐条插入 --INSERT INTO user (name, email) VALUES (#{name}, #{email})!-- 最快批量插入 --INSERT INTO user (name, email) VALUESforeachcollectionlistitemitemseparator,(#{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 1DISTINCT 适合简单的去重GROUP BY 适合需要统计的场景。四、实战场景题场景 1分页查询用户列表支持姓名模糊搜索和按创建时间排序selectidqueryUserPageresultTypeUserSELECT * FROM userwhereiftestname ! null and name ! name LIKE CONCAT(%, #{name}, %)/if/whereORDER BY create_time DESC/select// Service 层PageUserpagenewPage(pageNum,pageSize);LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();wrapper.like(StringUtils.isNotBlank(name),User::getName,name);wrapper.orderByDesc(User::getCreateTime);returnuserMapper.selectPage(page,wrapper);场景 2涉及多表的关联查询selectidgetOrderDetailresultMapOrderDetailMapSELECT 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}/selectMyBatis-Plus 不擅长多表关联查询复杂关联还是写 XML 更清晰。场景 3插入后需要返回主键insertidinsertUseruseGeneratedKeystruekeyPropertyidINSERT INTO user (name, email) VALUES (#{name}, #{email})/insertUserusernewUser();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/爬虫 实战干货不让你白来。