# JPA Fetch Type - Lazy 是無辜的 ## 定義 在 JPA 定義的 Enity 中有四種關連 1. @ManyToOne 2. @OneToOne 3. @ManyToMany 4. @OneToMany ```java @Entity public class UserBrowsingHistory{ @Id long id; @Basic Instant createdTime; @ManyToOne User user; } ``` 在這個 annotation 背後還有些小小的細節存在 ```java @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ManyToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default FetchType.EAGER; boolean optional() default true; } ``` ```java @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ManyToMany { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default FetchType.LAZY; String mappedBy() default ""; } ``` 眼尖的大家應該有注意到,他們裡面有個 **FetchType** 而且預設值不同,一個是 eager 一個則是 lazy。 ## 為什麼要區分? 如果我們再仔細一點觀察,會發現結尾是 toOne 的關聯中,預設都是 eager,反之結尾是 toMany 的關聯,預設都是 lazy。 以上述的 UserBrowsingHistory 來說,每筆瀏覽紀錄都會對應到一個使用者,當有 100 條的瀏覽紀錄,就會關聯到最多 100 位的使用者。 而因為效能上面的考量,假設每一筆的瀏覽紀錄有辦法對應到多個使用者,那這次的查詢資料大小將會有可能膨脹得非常大,所以預設會是 lazy ,希望你真正需要使用到的時候才去資料庫進行查詢,一方面將查詢的效能控制在一個範圍內,也避免一些無意義的資料浪費。 ## 反思 那怎麼知道應該要使用 eager 還是 lazy 才是對的呢? 我認為一開始的使用情境都應該要預設是 lazy ,這樣才能避免無謂的資料被查詢出來,如果開發者清楚在接下來的流程中會使用到相關的資料,再去使用 `join fetch`, `where in (...)` 之類的方式查詢出相關的資料。 今天有一個需求就是需要根據瀏覽紀錄的 id 去找出對應的瀏覽時間,來呈現在前端頁面上,但你可能會看到下面這樣的 SQL。 ```sql select * from user_browsing_history where id in (1, 2, 3); ``` 這樣的 SQL 本身沒有問題,他可以滿足這項需求,但是過程中它從資料庫中取得了過多它所不需要的資訊 1. 增加查詢時間(因為資料的大小變大了 2. 增加記憶體的使用量 (因為資料的大小變大了 所以在這樣的考量下,我們去針對使用的場景,避免掉一些不必要的查詢,所以何時該用 lazy, eager fetch 的答案也就呼之欲出了。 ## 背負臭名的 lazy fetch 大家通常罵最兇的就是 lazy fetch,因為它會導致以下的 N+1 query 的問題 ```java @Entity public class UserBrowsingHistory{ @Id long id; @Basic Instant createdTime; @ManyToOne(fetch = FetchType.LAZY) User user; } ``` ```java public interface UserBrowsingHistoryRepository extends JpaRepository<UserBrowsingHistory, Long>{ } ``` ```java private final UserBrowsingHistoryRepository repository; public void printAllBrowsingUserName(){ final List<UserBrowsingHistory> userBrowsingHistories = repository.findAll(); // select * from user_browsing_history; userBrowsingHistories.forEach(this::printUserName); /* * select * from "user" where id = ?; * select * from "user" where id = ?; * select * from "user" where id = ?; */ } ``` 實際上這不完全是個錯誤!如果在沒有需要使用 user data 的情況下,lazy fetch 幫我們節省了記憶體、查詢時間,只能說是在一開始做開發的時候,沒有思考清楚自己應該使用的查詢策略。 ###### tags: `blog` `java` `JPA`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up