Spring

使用 Entity Graph 防止 N+1 問題

蔡仔為 Victor Tsai 2023/08/07 18:59:42
2797

今天想分享 Spring data jpa 中常見的 N+1 問題以及如何防止這類問題使得 API 效能降低

 

文章大綱如下:

1. 什麼是  N+1 問題

2. 什麼是 Entity Graph

3. 如何用 entityGraph 防止 N+1 問題

4. entityGraph 有什麼缺陷

 

什麼是  N+1 問題

可以試想一個情境,當你在活動平台中報名不同活動,假設目前已經有 3 筆報名的資料。

其中報名的資料中也有數筆報名資料的歷史紀錄檔在記錄狀態的變化,此時如果網頁有一個需求是除了要查看報名的資料,

也需要提供 user 查看歷史的情況下,api 會用到 4 次 SQL。

第1個SQL : 撈取報名資料

第 2~N+1 個SQL : 查詢各別的報名歷史紀錄

在資料量暴增的情況下,會影響到  api 的資料查詢方式,假如有 10000 筆資料且 SQL 的執行時間約為 0.3s 有,此時會執行超過 1 分鐘造成效能拖累。

 

什麼是 Entity Graph

Spring Data JPA的Entity Graph是用於解決查詢N+1問題的強大工具。透過明確指定要一起檢索的關聯屬性

它可以有效地減少資料庫查詢次數,提高查詢效能,並確保應用程式的高效運行。

 

如何用 entityGraph 防止 N+1 問題

在這邊我想要用作者跟他的書作為一個例子,這個作者他會出版若干本書,因此作者與書本的關係為一對多,Java Entity 如下

/**
 * 作者的 Entity
 */
@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // books 屬性如果沒有指定都會是 LAZY (不會自動加載)
    @OneToMany(mappedBy = "author")
    private List<Book> books;

    // Getter和Setter
}
/**
 * 作者出版的書本
 */

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;

    // Getter和Setter
}

Repository:

/**
 * 查詢作者列表
 */
@Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
    

/**
 * 查詢作者以及其書籍
 */
    @EntityGraph(attributePaths = "books")
    Optional<Author> findWithBooksById(Long id);

/**
 * 僅查詢作者
 */
    List<Author> findAll();
}

 

如此特性下,JPA 就可以提供一個彈性較佳的查詢方式,也可以依照不同的業務邏輯去查詢資料庫的資料

 

entityGraph 有什麼缺陷

EntityGraph 是一個有用的查詢優化工具,同時在某些情境下也會成為雙面刃。首先,過度關聯性可能是一個問題,當定義的 EntityGraph 包含大量關聯實體時,查詢可能變得過於複雜和冗長,進而影響效能。其次,使用 EntityGraph 需要精確地定義需要檢索的關聯屬性,但這可能因用途而異,導致在某些情況下未能提供最佳效能。而在其他情況下,EntityGraph 可能無法解決所有的 N+1 問題,尤其是在資料庫關聯設計較複雜的情況。

 

總結:

Spring-data-jpa 提供的 EntityGraph 可以在一些情境下將查詢的效能增加,但是有些情形不見得適用,因此也需要多方評估後再決定是否需要使用

 

蔡仔為 Victor Tsai