大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Hibernate 深入
orm-essentials 教了 @Entity 与 EntityManager 的入门用法;本课补 Session 生命周期、懒加载陷阱、N+1 与二级缓存 等 Hibernate 特有细节,让你在 spring-boot-essentials 里不再「注解会写、SQL 爆炸不懂」。
学习目标
- 理解 Session / EntityManager 与持久化上下文(Persistence Context)
- 识别并修复 N+1 查询与 LazyInitializationException
- 掌握
@OneToManyfetch 策略与join fetch的取舍
实体与关系映射
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();
public void addItem(OrderItem item) {
items.add(item);
item.setOrder(this);
}
}
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String sku;
private int qty;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
}
双向关联时由「拥有外键的一方」维护关系;mappedBy 侧只读映射。
N+1 与 join fetch
// 坏味道:查 1 条 Order,再懒加载 N 次 items → N+1
Order order = em.find(Order.class, 1L);
order.getItems().size();
// 修复:一次 JPQL 拉齐
List<Order> orders = em.createQuery(
"select distinct o from Order o join fetch o.items where o.code = :code",
Order.class)
.setParameter("code", "ORD-001")
.getResultList();
| 现象 | 原因 | 对策 |
|---|---|---|
| LazyInitializationException | Session 已关仍访问懒集合 | @Transactional 内完成访问,或 DTO 投影 |
| 日志里 1+N 条 SELECT | 默认 LAZY + 循环触达 | join fetch / @EntityGraph / batch size |
| distinct 重复行 | join 笛卡尔积 | JPQL 加 distinct,或改 DTO 查询 |
Session 与 Spring 事务
Hibernate 的 Session 在 JPA 下由 EntityManager 包装。Spring 的 @Transactional 在方法入口绑定 Session、出口 flush 并关闭——事务边界 = 持久化上下文边界。
@Service
public class OrderService {
@PersistenceContext
private EntityManager em;
@Transactional(readOnly = true)
public OrderDto getOrder(String code) {
Order order = em.createQuery(/* join fetch */, Order.class)
.setParameter("code", code)
.getSingleResult();
return OrderDto.from(order); // 在事务内完成映射
}
}
练习
- 开启
hibernate.show_sql,复现一次 N+1,再用 join fetch 对比 SQL 条数 - 写一个会触发 LazyInitializationException 的测试,并用
@Transactional修复 - 阅读 java-core-essentials 的异常处理,为
NonUniqueResultException设计友好错误
小结
Hibernate 的威力在对象图导航,代价是 SQL 不可见。 掌握 Session 边界与 fetch 策略,ORM 才是助力而非黑盒。下一课看轻量替代方案 EBean。