JPA 預設採用 LAZY(延遲載入),實際存取時才發送 SQL。本文以 Garage(車庫)與 Auto(汽車)一對多關聯為例,實測兩種策略的 SQL 差異與效能影響。
// Garage.java
@Entity
public class Garage {
@Id @GeneratedValue
private Integer gid;
@Column(length=20)
private String garagenum;
@OneToMany(cascade=CascadeType.PERSIST, mappedBy="garage")
private Set<Auto> autos = new HashSet<>();
// getter/setter/addGarageAuto...
}
// Auto.java
@Entity
public class Auto {
@Id @GeneratedValue
private Integer autoId;
private String autotype, autonum;
@ManyToOne
@JoinColumn(name="garageid")
private Garage garage;
// getter/setter...
}
@Test
public void query() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-hibernate");
EntityManager em = emf.createEntityManager();
Garage garage = em.find(Garage.class, 1); // 載入 ID=1 的車庫
em.close();
emf.close();
}
執行 SQL:
Hibernate: select garage0_.gid as gid1_0_, garage0_.garagenum as garagenum1_0_
from Garage garage0_ where garage0_.gid=?
**僅查詢 Garage**,autos 集合為 Proxy,未觸發關聯查詢。若後續呼叫 garage.getAutos() 再發送第二條 SQL。
@OneToMany(cascade=CascadeType.PERSIST, fetch=FetchType.EAGER, mappedBy="garage")
public Set<Auto> getAutos() { return autos; }
執行 SQL:
Hibernate: select garage0_.gid as gid1_1_, garage0_.garagenum as garagenum1_1_,
autos1_.garageid as garageid3_, autos1_.autoId as autoId3_,
autos1_.autoId as autoId0_0_, autos1_.autonum as autonum0_0_,
autos1_.autotype as autotype0_0_, autos1_.garageid as garageid0_0_
from Garage garage0_
left outer join Auto autos1_ on garage0_.gid=autos1_.garageid
where garage0_.gid=?
**單條 SQL 完成主從關聯查詢**(LEFT OUTER JOIN),autos 即時填充。
| 維度 | LAZY (預設) | EAGER |
|---|---|---|
| SQL 數量 | 1+N (N=關聯集合存取次數) | 1 (JOIN) |
| 記憶體佔用 | 低(按需載入) | 高(一次性載入全圖) |
| 適用場景 | 關聯不常用、大集合、效能敏感 | 關聯極小、幾乎總是被使用 |
| 風險 | Session 關閉後存取拋出 LazyInitializationException | 過度載入導致記憶體/網路浪費 |
JOIN FETCH 或 fetch=EAGEREAGER——耦合查詢策略,失去彈性@NamedEntityGraph + EntityManager.find(..., hints) 或 JPQL JOIN FETCH@LazyCollection(LazyCollectionOption.EXTRA)極大集合時,size()/contains() 不載入全集合,改發 COUNT/EXISTS SQL,更精細控制。
抓取策略無銀彈,**按使用場景動態組合**才是正道。