# 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
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.