博客 / 詳情

返回

Spring Data JPA 中的 Projection 基礎學習:從官方文檔到實踐理解

最近諮詢老師的過程中,老師的耐心回覆中提到了Spring Data的一個詞:Projection 。我才疏學淺,在之前的學習過程中從未聽聞或瞭解過。在搜索後,十分羞愧於,這樣基本的概念竟然還沒有理解和掌握。因此,以此篇文章來記錄自己學習的過程。

在計算機領域,我始終認為:
學習一個陌生概念的最佳途徑,就是從官方文檔開始。

引用官方文檔原文
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to create projections based on certain attributes of those types. Spring Data allows modeling dedicated return types, to more selectively retrieve partial views of the managed aggregates.

文檔中的關鍵信息是:

Projection 用來獲取實體的部分字段,而非整個實體本身。

先來看官方提供的示例:

public class Person {
  @Id
  private UUID id;
  private String firstname;
  private String lastname;
  private Address address;

  public static class Address {
    private String city;
    private String street;
  }
}

public interface PersonRepository extends Repository<Person, UUID> {
  Collection<Person> findByLastname(String lastname);
}

一、為什麼需要 Projection?

默認情況下,Spring Data Repository 查詢會返回完整實體對象(Aggregate Root)。
但這帶來一些現實問題:

1. 不必要的字段加載

如果你只需要 firstname,但系統卻加載了 Person 的全部字段(包括 Address),這是浪費內存與網絡帶寬。

2. 性能問題(例如 N+1 查詢)

返回完整實體時,ORM 可能觸發遞歸加載關聯對象,導致多次 SQL 查詢。

3. 前端接口返回結構冗餘

一些接口只需要部分字段,但返回完整實體需要手動過濾,非常繁瑣。

因此,引入 Projection 能夠:

  • 明確“只查我想要的字段”
  • 降低數據量
  • 提升性能
  • 使 DTO 結構更清晰

二、Spring Data JPA 的 Projection 類型

Spring Data JPA 提供三類 Projection,分別適用於不同場景。


1. 基於接口的 Projection(Interface-based Projections)

最常用,也最靈活。

1.1 閉合投影(Closed Projection)

特點:

  • 所有 getter 與實體字段一一對應
  • Spring 可以生成精確 SQL,只選擇必要字段
  • 性能最佳

示例:

public interface NamesOnly {
  String getFirstname();
  String getLastname();
}

這是最推薦的投影方式。


1.2 開放投影(Open Projection)

特點:

  • 通過 @Value + SpEL(Spring Expression Language,Spring 表達式語言) 計算新字段
  • 靈活,但可能導致加載全部字段(無法優化 select)

示例:

public interface NamesOnly {
  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
}

適用場景:

  • 想快速組合字段,不想新建 DTO
  • demo、小項目、小數據量適用
  • 想調用 Bean 中的方法做格式化或業務邏輯

示例(調用 Bean):

@Value("#{@myBean.getFullName(target)}")
String getFullName();

2. 基於類的 Projection(Class-based / DTO Projections)

即使用 POJO(Plain Old Java Object) 或 Record 來接收查詢字段。

特點:

  • 需要提供順序匹配、類型匹配的構造函數
  • 無代理對象,返回真實 DTO
  • 不支持嵌套投影
  • 結構清晰、編譯期類型檢查友好

示例:

public class NamesOnly {
  private final String first;
  private final String last;

  public NamesOnly(String first, String last) {
    this.first = first;
    this.last = last;
  }
}

Record 示例(扁平化字段):

public record PersonDto(String firstname, String city, String street) {}

當你想保證 DTO 乾淨、無邏輯、強類型時,這是最佳方案。


3. 動態投影(Dynamic Projections)

特點:

  • 通過泛型 <T> 實現“一個方法返回多種類型”
  • 運行時動態指定 Projection 類型

Repository 示例:

public interface PersonRepository extends Repository<Person, UUID> {
  <T> Collection<T> findByLastname(String lastname, Class<T> type);
}

調用方式:

people.findByLastname("Li", Person.class);
people.findByLastname("Li", NamesOnly.class);
people.findByLastname("Li", PersonDto.class);

優勢:

  • 減少重複方法
  • 靈活度最高
  • 常用於通用查詢接口

三、不同 Projection 的使用建議

類型 優點 缺點 建議場景
Closed 投影(接口) 性能最佳、最簡單 不能計算字段 接口返回部分字段
Open 投影(接口 + SpEL) 靈活、可組合字段 性能差、難維護 小數據量、臨時需求、簡單拼字段
類/Record 投影(DTO) 強類型、可擴展、乾淨 需要構造函數,不支持嵌套 生產系統返回 DTO
動態投影 一個方法支持多種返回 調用時更復雜 通用查詢 API 設計

四、總結

道阻且長,行則將至;行而不輟,未來可期。 ——《荀子·修身》

本文只基於官方文檔進行 Projection 最基礎的理解和分享,僅作學習記錄,如有錯誤或偏頗,歡迎批評指正。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.