ORM 面试题
本页只放 ORM 面试标准回答、项目话术、常见追问和知识点跳转。MyBatis、Hibernate、JPA、缓存、动态 SQL、拦截器、事务和 SQL 优化的详细原理放到知识点页。
使用方式
mermaid
flowchart TD
A["面试页:回答差异和风险"] --> B["知识点页:理解 SQL 执行和映射原理"]
B --> C["项目页:数据访问、事务、性能排查"]高频问题
| 面试题 | 标准回答 | 原理知识点 |
|---|---|---|
| 怎么判断 ORM 是否真正学懂 | 不能只会 Mapper、Wrapper 或 Repository API。真正学懂要能从 JDBC 痛点、MyBatis Mapper 代理、MappedStatement、BoundSql、动态 SQL、参数绑定、结果映射、缓存、插件、MyBatis-Plus、JPA 持久化上下文、脏检查、懒加载、N+1、事务边界、批量写入和慢 SQL 排查一路讲下来,并能说明每个环节为什么这样设计、不这样会出什么生产问题。 | ORM从零到精通验收清单 |
| ORM 应该怎么从零学到生产级 | 不能只学 Mapper 或 Repository API,要按 JDBC 痛点、参数绑定、SQL 执行、结果映射、缓存、事务、批量写入、JPA 持久化上下文、慢 SQL 排查这条链路学习。 | ORM从零到生产级掌握 |
| ORM 是什么 | ORM 是对象关系映射,把 Java 对象和关系型数据库表之间的读写转换起来,减少手写 JDBC 的重复代码。 | ORM总览、JPA基础 |
| MyBatis 和 Hibernate 区别 | MyBatis 是半自动 ORM,SQL 主要由开发者控制,适合复杂 SQL 和性能可控场景;Hibernate 更强调实体状态、持久化上下文、脏检查和对象关系映射,CRUD 开发快,但复杂 SQL 要关注最终生成语句。 | MyBatis核心全过程原理、Hibernate/JPA核心全过程原理 |
| MyBatis 为什么 Mapper 接口能执行 | MyBatis 会为 Mapper 接口创建代理对象,调用方法时根据 namespace、方法名和参数找到 MappedStatement,动态 SQL 生成 BoundSql,再通过 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 完成执行与映射。 | 商业场景训练营、MyBatis核心全过程原理 |
#{} 和 ${} 区别 | #{} 使用预编译参数,能防 SQL 注入;${} 是字符串拼接,适合动态表名、字段名、排序字段,但必须做白名单校验。 | MyBatis动态SQL |
| MyBatis 一级缓存和二级缓存 | 一级缓存是 SqlSession 级别,默认开启;二级缓存是 namespace 级别,需要配置。生产中二级缓存要谨慎,因为多表更新、分布式部署容易带来一致性问题。 | MyBatis缓存机制 |
| N+1 查询是什么 | 先查询一批主对象,再循环访问懒加载关联,每条主对象再触发一条 SQL,导致 SQL 次数从 1 变成 1+N。可用 DTO 投影、fetch join、批量抓取或手写 join 解决。 | Hibernate/JPA核心全过程原理、Spring Data JPA查询 |
| MyBatis 插件能做什么 | 插件通过动态代理拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler,可做分页、慢 SQL、数据权限、字段加密等横切增强。 | MyBatis拦截器 |
| MyBatis 动态 SQL 最后怎么执行 | <if>、<where>、<foreach> 等标签先根据参数生成最终 SQL 和参数映射,形成 BoundSql,后续仍然通过 PreparedStatement 执行,不是在数据库里执行 XML 标签逻辑。 | MyBatis核心全过程原理、MyBatis动态SQL |
| BoundSql 是什么 | BoundSql 是 MyBatis 动态 SQL 处理后的运行时 SQL 对象,里面包含最终 SQL 文本、参数映射、参数对象和附加参数。排查动态 SQL 时要看 BoundSql,因为 XML 只是模板,数据库真正执行的是 BoundSql 里的 SQL。 | MyBatis动态SQL:动态SQL本质、MyBatis核心全过程原理 |
MyBatis 动态 SQL 为什么要用 where 和 set | where 会在有条件时生成 where,并去掉开头多余的 and/or;set 会生成 update set 并去掉结尾多余逗号。它们能减少动态 SQL 拼接错误,但不能替代业务校验。更新和删除仍要校验关键条件,避免全表更新或删除。 | MyBatis动态SQL:where/set/trim |
foreach 有什么坑 | foreach 常用于 in 查询和批量插入,但集合为空可能生成非法 SQL,集合太大会导致 SQL 很长、参数过多、优化器估算不准和网络包变大。生产中要先判断空集合,并控制批大小,超大批量考虑临时表、中间表或批任务。 | MyBatis动态SQL:foreach边界 |
| 动态排序为什么容易 SQL 注入 | 排序字段属于 SQL 结构,不能用 #{} 作为字段名,很多人会用 ${} 拼接;${} 是字符串替换,如果直接接收前端输入,会改变 SQL 结构造成注入。动态排序必须在 Java 层把字段名和排序方向做白名单映射。 | MyBatis动态SQL:排序白名单 |
| MyBatis 事务为什么放 Service 层 | Service 层代表一个完整业务动作,可能包含多个 Mapper 调用。事务放在 Mapper 层只能覆盖单条数据访问,无法保证订单、明细、日志等多个写操作一起提交或回滚。 | 商业场景训练营、MyBatis核心全过程原理、事务与一致性 |
| MyBatis 查询有数据但对象字段为空怎么办 | 先确认 SQL 返回列,再检查列别名、驼峰映射、resultMap 的 column/property、类型处理器和多表 join 的重复列名。 | MyBatis核心全过程原理 |
| MyBatis 一级缓存为什么第二次不查库 | 一级缓存是 SqlSession 级别,默认开启。同一个 SqlSession 内相同 MappedStatement、相同 SQL、相同参数、相同分页信息可能命中本地缓存。执行写操作、commit、rollback、close 会清空一级缓存。Spring 项目中不要为了缓存而扩大事务或 SqlSession 生命周期。 | MyBatis缓存机制:一级缓存生命周期 |
| MyBatis 二级缓存为什么慎用 | 二级缓存是 Mapper namespace 级别,多个 SqlSession 可共享,但它很难感知其他 namespace、多表关联、分布式节点和外部系统对数据的修改,容易读到旧数据。强一致业务通常不建议依赖 MyBatis 二级缓存,业务缓存应显式设计 Redis 和失效策略。 | MyBatis缓存机制:二级缓存一致性 |
| MyBatis-Plus 是什么 | MyBatis-Plus 是 MyBatis 增强工具,提供通用 CRUD、Wrapper 条件构造器、分页插件、逻辑删除、自动填充、乐观锁、代码生成等能力,但复杂 SQL 仍要回到 MyBatis 和 SQL 本身。 | MyBatis-Plus核心全过程原理、MyBatis-Plus学习路线 |
| MyBatis-Plus Wrapper 原理是什么 | Wrapper 会把 eq、like、in、orderBy 等链式调用记录成 SQL 条件片段和参数,拼进 MyBatis-Plus 注入的通用 SQL 模板,最后仍然形成 MyBatis 的 BoundSql 并由 JDBC 执行。它提高简单单表查询效率,但不能替代 SQL 和索引分析。 | MyBatis-Plus Wrapper |
| QueryWrapper 和 LambdaQueryWrapper 区别 | QueryWrapper 使用字符串字段名,写法直观但重构不安全;LambdaQueryWrapper 使用方法引用,框架会解析实体属性再映射到数据库列名,重构更安全。但 LambdaWrapper 只是可维护性更好,不代表 SQL 执行更快。 | MyBatis-Plus Wrapper:两者区别 |
Wrapper 中 or 有什么坑 | 如果 or 没有分组,可能破坏前面的租户、医院、逻辑删除、权限条件,导致查询范围扩大甚至越权。关键字搜索这类 (name like ? or code like ?) 应该用 and(w -> ...) 包裹,确保 SQL 语义正确。 | MyBatis-Plus Wrapper:and/or 分组 |
Wrapper 的 last 为什么危险 | last 会把字符串直接追加到 SQL 末尾,绕过参数绑定。如果接收前端输入,可能造成 SQL 注入或破坏 SQL 结构。生产中只允许固定常量,分页优先用分页插件,不要把 last 封装成可传任意片段的公共能力。 | MyBatis-Plus Wrapper:last 风险 |
| MyBatis-Plus 分页插件原理 | 分页插件通过 MyBatis 插件机制拦截 SQL,根据数据库方言生成 count SQL 和分页 SQL。分页慢通常要排查 count、深分页、排序索引、扫描行数和回表。 | MyBatis-Plus核心全过程原理 |
| MyBatis-Plus 逻辑删除有什么坑 | 逻辑删除会把 delete 改成 update 标记,并给查询追加未删除条件。它会影响唯一索引、历史查询、表体积和统计逻辑,不能只加注解不做表设计。 | MyBatis-Plus核心全过程原理 |
| MyBatis 拦截器能拦哪些对象 | MyBatis 插件可以拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler。Executor 适合统计和审计入口,StatementHandler 适合分页和 SQL 改写,ParameterHandler 适合参数处理,ResultSetHandler 适合结果处理。插件是横切增强,不适合写复杂业务逻辑。 | MyBatis拦截器:拦截位置 |
| 分页插件为什么不能解决深分页慢 | 分页插件只能把 SQL 改写成数据库分页语法,不能改变数据库需要扫描和跳过大量行的事实。深分页慢要从游标分页、索引、查询条件、异步导出和业务交互上优化,不能只依赖插件。 | MyBatis拦截器:分页插件原理、MySQL大表覆盖索引优化 |
| 数据权限插件有什么风险 | 数据权限插件会隐式改写 SQL,复杂 SQL、别名、子查询、分页 count、不同角色规则都可能出错;追加的权限条件如果没有索引支持,还会拖慢所有查询。商业项目里插件适合统一兜底,复杂权限最好显式建模并配套测试和审计。 | MyBatis拦截器:数据权限 |
| JPA 和 Hibernate 关系 | JPA 是 Java 持久化规范,定义 Entity、EntityManager、持久化上下文、JPQL 等标准;Hibernate 是常见实现,真正负责实体状态管理、脏检查、SQL 生成和懒加载。 | Hibernate/JPA核心全过程原理、JPA基础 |
| Spring Data JPA 做了什么 | 它进一步封装 JPA Repository,启动时为接口创建代理,能根据内置方法、方法名、@Query、Specification 生成查询入口,底层仍然通过 EntityManager 和 Hibernate 工作。 | Spring Data JPA核心全过程原理、Hibernate/JPA核心全过程原理 |
| Repository 接口为什么能执行 | Spring Data JPA 启动时扫描 Repository 接口,根据泛型识别实体和主键类型,为接口创建代理对象。调用方法时代理判断是内置 CRUD、方法名查询、@Query 还是自定义实现,再调用 EntityManager。 | Spring Data JPA核心全过程原理 |
| 方法名查询原理是什么 | 框架会解析 findByStatusAndNameContaining 这类方法名,把操作类型、实体属性、连接词和查询关键字转换成查询模型,最后交给 Hibernate 生成 SQL。复杂条件不建议继续拉长方法名。 | Spring Data JPA核心全过程原理、Spring Data JPA查询 |
| Page 和 Slice 区别 | Page 会查总数和当前页数据,能返回总页数;Slice 通常不查总数,只判断是否有下一页。大表或信息流场景更适合 Slice 或游标分页。 | Spring Data JPA核心全过程原理、事务与分页 |
| JPA 为什么修改实体不用 update | 事务内查询出来的实体处于托管态,持久化上下文保存快照;提交前 flush 做脏检查,发现字段变化后由 Hibernate 生成 update SQL。 | Hibernate/JPA核心全过程原理 |
| flush 和 commit 区别 | flush 是把持久化上下文的变更同步成 SQL 发给数据库;commit 是提交数据库事务。flush 后仍可回滚,commit 后事务才真正结束。 | Hibernate/JPA核心全过程原理 |
| JPA 实体有哪些状态 | 常见状态有临时态、托管态、游离态、删除态。new 出来的对象是临时态,persist 或查询返回后进入托管态,事务结束或 detach 后变成游离态,remove 后进入删除态。只有托管态实体的变化会被持久化上下文脏检查自动同步。 | JPA基础:实体状态、Hibernate核心概念 |
| 持久化上下文是什么 | 持久化上下文是事务内的实体管理区,保存托管实体、实体快照、待执行动作和一级缓存。它让 JPA 能做同一事务内实体复用、脏检查、延迟写入和懒加载。 | JPA基础:持久化上下文、Hibernate核心概念 |
| merge 为什么要谨慎 | merge 会把游离对象状态合并到托管对象,如果请求对象字段不完整,null 也可能覆盖数据库已有字段。生产中更推荐先按 id 查询托管实体,再调用有业务含义的方法修改指定字段。 | JPA基础:merge风险、Hibernate核心概念:merge |
| JPA 懒加载为什么会失败 | 懒加载通过代理实现,访问关联字段时需要持久化上下文还存在。事务结束后 EntityManager 关闭,再访问懒加载字段就可能出现 LazyInitializationException。解决方式是在 Service 事务内转 DTO,或使用 fetch join / EntityGraph / DTO 投影。 | JPA基础:懒加载、Hibernate核心概念:懒加载代理 |
| Hibernate N+1 怎么解决 | N+1 是先查一批主实体,再循环访问懒加载关联导致额外 N 次 SQL。可用 DTO 投影、fetch join、EntityGraph 或先批量查关联数据再组装。列表页更推荐 DTO 投影,避免加载不需要的实体图。 | Hibernate核心概念:N+1、Spring Data JPA查询 |
| 为什么不建议返回 Entity | Entity 是持久化模型,直接返回容易暴露字段、触发懒加载异常、造成循环引用和隐式 SQL。接口层应返回 DTO。 | Hibernate/JPA核心全过程原理、Spring Data JPA实战建议 |
| 事务边界放哪里 | 通常放在 Service 层,覆盖一个完整本地数据库业务动作。事务不能过大,也不要把慢远程调用、MQ 发送、耗时计算放进事务里。 | ORM事务与一致性、Spring事务 |
| Spring 事务为什么失效 | 常见原因是同类自调用没经过代理、异常被 catch 吞掉、受检异常未配置 rollbackFor、方法不是 public、多数据源事务管理器不一致、手动连接绕过 Spring。 | ORM事务与一致性、Spring事务 |
| Spring Data JPA 方法名查询有什么边界 | 方法名查询适合简单固定条件,条件一多方法名会非常长,可读性和维护性变差。复杂固定查询用 @Query,动态组合条件用 Specification 或 Querydsl,列表接口优先 DTO 投影。 | Spring Data JPA查询:方法名边界 |
| JPQL 和原生 SQL 区别 | JPQL 面向实体名和属性名,可移植性较好;原生 SQL 面向表名和字段名,可控性更强、适合数据库特性和复杂 SQL,但可移植性差,字段映射要自己关注。 | Spring Data JPA查询:JPQL和原生SQL |
| DTO 投影为什么推荐 | DTO 投影只查询接口需要字段,避免 Entity 字段暴露、懒加载、循环引用和多余 SQL。列表页、详情页接口应优先返回 DTO,而不是直接返回 Entity。 | Spring Data JPA查询:DTO投影 |
| Page 为什么可能慢 | Page 通常执行数据查询和 count 查询,大表、复杂 join、group by、distinct 场景下 count 可能很慢。不需要总数时用 Slice;复杂查询可以手写更简单的 countQuery。 | 事务与分页:Page count、Spring Data JPA查询:Page慢 |
| Slice 和 Page 区别 | Page 返回当前页、总条数、总页数,通常要查 count;Slice 只返回当前片段和是否有下一页,通常不查 count,适合信息流、日志、采集记录滚动加载。 | 事务与分页:Slice |
| MyBatis 和 JPA 事务差异 | MyBatis 通常调用 Mapper 时执行 SQL;JPA/Hibernate 会管理托管实体,提交前 flush 脏检查生成 SQL。两者最终都依赖 Spring 绑定到当前线程的数据库连接提交或回滚。 | ORM事务与一致性 |
| 事务里为什么不能直接发 MQ | MQ 不受本地数据库事务控制,可能消息发出后数据库回滚,也可能数据库提交后消息发送失败。可用 afterCommit 或本地消息表,消费端要幂等。 | ORM事务与一致性、可靠消息最终一致性 |
| SQL 慢怎么排查 | 先定位慢 SQL,再看执行计划、索引命中、扫描行数、排序、分页、关联方式、锁等待和数据库资源。 | EXPLAIN执行计划、MySQL优化 |
项目话术
text
在医疗数据采集平台里,采集落库和资产查询通常用 MyBatis 或 MyBatis-Plus,因为字段映射、批量写入、动态条件查询和复杂统计 SQL 比较多。事务边界放在 Service 层,批量采集要控制批大小,慢 SQL 会通过日志、执行计划和索引分析排查。对于简单后台 CRUD,也可以用 JPA 或 Spring Data JPA,但要持续关注最终 SQL。常见追问
| 追问 | 回答方向 | 跳转 |
|---|---|---|
| 为什么不建议随便开二级缓存 | 多表关联、分布式部署、更新路径复杂时容易读到旧数据。 | MyBatis缓存机制 |
${} 一定不能用吗 | 不是,动态字段和排序可能需要,但必须白名单。 | MyBatis动态SQL |
| JPA 懒加载为什么容易报错 | 实体离开持久化上下文后再访问懒加载字段,会没有 Session 可用。 | Hibernate/JPA核心全过程原理 |
| 批量插入怎么优化 | 不要用 for 循环逐条 insert。MyBatis 常见方案是 foreach 拼多 values 或 ExecutorType.BATCH。foreach 简单但 SQL 不能过长;BatchExecutor 更适合大量重复写入,但要定期 flush、控制内存和事务。MySQL 还要关注 rewriteBatchedStatements=true。 | MyBatis批量操作 |
foreach 和 ExecutorType.BATCH 区别 | foreach 是动态 SQL 在 MyBatis 侧拼成一条多 values SQL;ExecutorType.BATCH 是 JDBC 批处理,把同一 SQL 的多组参数累积后 executeBatch。前者写法简单,后者适合大批量但更考验 flush、提交、失败处理。 | MyBatis批量操作:两者对比 |
| 批量导入事务怎么设计 | 不要十万条一个事务。更合理的是创建导入任务,解析校验后按 500/1000 等批大小落库,每批独立事务,记录成功数和失败明细。失败时可拆小批或单条定位,并用唯一索引或业务流水号保证幂等。 | MyBatis批量操作:事务边界、ORM事务与一致性 |
| 批量导入部分失败怎么办 | 先做格式校验,把明显错误行记录下来;批量 SQL 失败后降级为小批或单条定位,记录行号、原因和原始数据,支持用户修正后重传。不能只返回系统异常,也不能因为一行坏数据让整个大文件不可追踪。 | MyBatis批量操作:部分失败 |
| 批量写入为什么会拖垮数据库 | 批量过大可能导致 SQL 包过大、事务锁时间变长、redo/binlog 或事务日志压力上升、连接长期占用、唯一键冲突反复回滚。优化要看批大小、事务、索引、锁等待、数据库日志和连接池,而不只是换一个 MyBatis 标签。 | MyBatis批量操作:线上排查 |
面试回答模板
text
ORM 我会先区分 MyBatis 和 Hibernate。MyBatis 更偏 SQL 可控,适合复杂查询和批量处理;Hibernate/JPA 更偏实体状态管理,CRUD 方便但要看最终 SQL。项目里我会把事务放在 Service 层,Mapper 只负责数据访问;动态 SQL 使用 #{ } 预编译,必须拼接字段时做白名单;慢 SQL 按日志、执行计划、索引、分页、关联和锁等待逐层排查。深度验收跳转
如果面试官继续追问原理,不要只停留在“我会 MyBatis 和 JPA”。按 ORM 从零到精通验收清单 回到知识点页,把 Mapper 代理、SQL 生成、参数绑定、缓存一致性、插件拦截、JPA 脏检查、N+1、事务失效、批量导入和慢 SQL 排查讲完整。
本章小结
ORM 面试页负责“怎么说”。Mapper 代理为什么能执行、缓存为什么有一致性风险、JPA 为什么会自动更新、SQL 为什么慢,都要去知识点页看原理、流程和 Demo。
