06.2 — JPA 高级与优化
定位: JPA 深度使用——N+1 问题、关联映射、缓存机制 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: N+1 问题定位与解决、关联映射策略、缓存机制
一、这是什么?为什么需要它?
JPA 虽然简化了数据访问,但错误的使用会导致性能问题(N+1)。本章深入 JPA 的优化技术和高级映射。
二、原理拆解
2.1 N+1 问题
java
@Entity
public class Post {
@Id
private Long id;
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments;
}
// N+1 查询
List<Post> posts = postRepository.findAll(); // 1 条查询
for (Post post : posts) {
post.getComments().size(); // N 条查询
}
// 总共 1+N 条 SQL解决方式
| 方案 | 代码 | 特点 |
|---|---|---|
| @EntityGraph | @EntityGraph(attributePaths = "comments") | 声明式,不修改 JPQL |
| JOIN FETCH | @Query("SELECT p FROM Post p LEFT JOIN FETCH p.comments") | 命令式,修改 JPQL |
| @BatchSize | @BatchSize(size = 10) | N次 -> ceil(N/10)次 |
@EntityGraph vs JOIN FETCH 区别
| 特性 | @EntityGraph | JOIN FETCH |
|---|---|---|
| 声明式/命令式 | 声明式,不修改 JPQL | 命令式,修改 JPQL |
| 复用现有查询 | 可组合到已有 @Query | 需要重写查询 |
| 多个关联 | 可指定多个 attributePaths | 多个 JOIN FETCH |
2.2 关联映射
| 注解 | 关系 | 维护方 | 默认 Fetch |
|---|---|---|---|
| @OneToOne | 一对一 | 持有外键的一方 | EAGER |
| @OneToMany | 一对多 | 多方(mappedBy) | LAZY |
| @ManyToOne | 多对一 | 多方持有外键 | EAGER(注意!) |
| @ManyToMany | 多对多 | 任意一方,需 JoinTable | LAZY |
建议:对于
@ManyToOne,显式设置fetch = FetchType.LAZY避免 N+1。
2.3 JPA 缓存
一级缓存(Persistence Context):
- 作用范围:每个 EntityManager / 每个事务
- 自动管理:事务提交时同步 dirty 对象
- 减少 SQL:同一事务内相同 ID 查询只发一次 SQL
二级缓存(Second-Level Cache):
- 作用范围:整个 SessionFactory / 所有事务共享
- 实现:需第三方(Ehcache、Redis)
- 配置:@Cacheable / @Cache(usage = ...)
- 场景:读多写少、变化不频繁的实体
三、面试视角
| 追问 | 答案要点 |
|---|---|
| N+1 问题产生的原因? | 遍历懒加载的关联集合时,每访问一次触发一次 SQL |
| @EntityGraph 和 JOIN FETCH 的区别? | EntityGraph 声明式不修改 JPQL,JOIN FETCH 命令式 |
| 一级缓存何时被清空? | 事务提交/回滚、clear()、close() 时 |
| @ManyToOne 的默认 Fetch 策略? | EAGER!建议改为 LAZY |
📚 相关链接
- **Spring Data JPA** — Repository 基础知识
- **事务管理** — JPA 事务管理
- ← 返回 **返回 数据访问索引**