CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
ORM 与数据库设计/课时/N+1 与抓取策略
RawBlame
课时目录12 节

ORM 与数据库设计

  1. 01ORM 是什么
  2. 02范式与规范化
  3. 03ER 模型
  4. 04JPA 与 Hibernate
  5. 05MyBatis
  6. 06Spring Data 家族
  7. 07Schema 迁移
  8. 08N+1 与抓取策略
  9. 09MariaDB 与 MySQL
  10. 10ORM 与事务
  11. 11反模式
  12. 12综合练习

N+1 与抓取策略

fetch join、@EntityGraph。

30 分钟

N+1 与抓取策略

加载 10 门课程再逐个查课时,JPA 可能发出 1 + 10 条 SQL——这就是 N+1 问题。懂抓取策略才能写不出「本地快、上线慢」的接口。

学习目标

  • 识别 N+1 的触发条件
  • 使用 JOIN FETCH、@EntityGraph
  • 对照 MyBatis 一次 JOIN 查询

问题复现

List<Track> tracks = trackRepository.findAll();  // 1 条 SQL
for (Track t : tracks) {
    t.getLessons().size();  // 每条 track 再 1 条 → N+1
}

日志里会看到:SELECT ... FROM cf_track,然后多次 SELECT ... FROM cf_lesson WHERE track_id = ?。

解法 1:JOIN FETCH

@Query("SELECT DISTINCT t FROM Track t LEFT JOIN FETCH t.lessons")
List<Track> findAllWithLessons();

一次 JOIN 拉回 Track + Lesson。注意 DISTINCT 防重复行。

解法 2:@EntityGraph

@EntityGraph(attributePaths = {"lessons"})
List<Track> findByCategory(String category);

声明本次查询需要预加载 lessons 关联。

抓取类型

类型行为默认
LAZY访问时才查@OneToMany
EAGER主查询后立即查慎用

原则:关联默认 LAZY;需要时在查询层显式 fetch,避免全局 EAGER 拖垮列表接口。

MyBatis 侧

MyBatis 无「隐式懒加载集合」——N+1 通常是你写了循环查 Mapper。复杂场景用一条 JOIN + resultMap 嵌套:

<select id="selectTrackWithLessons" resultMap="trackWithLessonsMap">
  SELECT t.*, l.id AS lid, l.title AS ltitle
  FROM cf_track t
  LEFT JOIN cf_lesson l ON l.track_id = t.id
  WHERE t.slug = #{slug}
</select>

相关课程

  • sql-join
  • sql-index
  • orm-mybatis

练习

  • 打开 SQL 日志(spring.jpa.show-sql),复现 N+1
  • 用 JOIN FETCH 改写 并对比 SQL 条数
findByCategory
  • 分页列表页为何不宜 JOIN FETCH 大集合(提示:内存与 DISTINCT)
  • 小结

    N+1 是 ORM 懒加载的代价;用 fetch 策略或 JOIN 一次取齐。 下一课讲 MariaDB 与 MySQL 兼容性。

    加载本节练习…
    ← 上一课Schema 迁移下一课 →MariaDB 与 MySQL