在 Spring Data JPA 中傳回 Map 而不是 List
1. 概述
Spring JPA 提供了非常靈活、方便的 API 來與資料庫互動。但是,有時,我們需要對其進行自訂或向返回的集合添加更多功能。
使用Map
作為 JPA 儲存庫方法的傳回類型可能有助於在服務和資料庫之間建立更直接的互動。不幸的是,Spring 不允許這種轉換自動發生。在本教程中,我們將檢查如何克服這個問題並學習一些有趣的技術來使我們的儲存庫更加實用。
2. 手動實施
當框架無法提供某些功能時,解決問題的最明顯方法是我們自己實現它。在這種情況下,JPA 允許我們從頭開始實作儲存庫,跳過整個生成過程,或使用預設方法來獲得兩全其美的效果。
2.1.使用List
我們可以實作一種方法將結果清單映射到映射中。 Stream API 對這項任務有很大幫助,幾乎允許單行實作:
default Map<Long, User> findAllAsMapUsingCollection() {
return findAll().stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
}
2.2.使用Stream
我們可以做類似的事情,但直接使用Stream
。為此,我們可以確定一個將返回用戶流的自訂方法。幸運的是,Spring JPA 支援此類返回類型,我們可以從自動生成中受益:
@Query("select u from User u")
Stream<User> findAllAsStream();
之後,我們可以實作一個自訂方法,將結果對應到我們需要的資料結構中:
@Transactional
default Map<Long, User> findAllAsMapUsingStream() {
return findAllAsStream()
.collect(Collectors.toMap(User::getId, Function.identity()));
}
傳回Stream
的儲存庫方法應該在事務內呼叫。本例中,我們直接在預設方法上加入了@Transactional
註解。
2.3.使用Streamable
這是與前面討論的方法類似的方法。唯一的變化是我們將使用Streamable
。我們需要創建一個自訂方法來首先返回它:
@Query("select u from User u")
Streamable<User> findAllAsStreamable();
然後,我們可以適當地映射結果:
default Map<Long, User> findAllAsMapUsingStreamable() {
return findAllAsStreamable().stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
}
3. 自訂Streamable
包裝器
前面的例子向我們展示了該問題的非常簡單的解決方案。然而,假設我們有幾種不同的操作或資料結構,我們想要將結果對應到這些操作或資料結構。在這種情況下,我們最終可能會得到分散在程式碼中的笨拙的映射器或執行類似操作的多個儲存庫方法。
更好的方法可能是創建一個表示實體集合的專用類,並將與集合上的操作相關的所有方法放置在其中。為此,我們將使用Streamable
。
如前所述,Spring JPA 理解Streamable
並可以將結果對應到它。有趣的是,我們可以擴展Streamable
並為其提供方便的方法。讓我們建立一個Users
類別來表示User
物件的集合:
public class Users implements Streamable<User> {
private final Streamable<User> userStreamable;
public Users(Streamable<User> userStreamable) {
this.userStreamable = userStreamable;
}
@Override
public Iterator<User> iterator() {
return userStreamable.iterator();
}
// custom methods
}
為了使其與 JPA 一起工作,我們應該遵循一個簡單的約定。首先,我們應該實作Streamable
,其次,提供 Spring 能夠初始化它的方式。初始化部分可以透過採用Streamable
公共建構函式或名稱of(Streamable<T>)
或valueOf(Streamable<T>)
靜態工廠來解決。
之後,我們可以使用Users
作為 JPA 儲存庫方法的回傳類型:
@Query("select u from User u")
Users findAllUsers();
現在,我們可以將儲存庫中保存的方法直接放在Users
類別中:
public Map<Long, User> getUserIdToUserMap() {
return stream().collect(Collectors.toMap(User::getId, Function.identity()));
}
最好的部分是我們可以使用與User
實體的處理或映射相關的所有方法。假設我們想按某些標準過濾掉用戶:
@Test
void fetchUsersInMapUsingStreamableWrapperWithFilterThenAllOfThemPresent() {
Users users = repository.findAllUsers();
int maxNameLength = 4;
List<User> actual = users.getAllUsersWithShortNames(maxNameLength);
User[] expected = {
new User(9L, "Moe", "Oddy"),
new User(25L, "Lane", "Endricci"),
new User(26L, "Doro", "Kinforth"),
new User(34L, "Otho", "Rowan"),
new User(39L, "Mel", "Moffet")
};
assertThat(actual).containsExactly(expected);
}
此外,我們可以透過某種方式對它們進行分組:
@Test
void fetchUsersInMapUsingStreamableWrapperAndGroupingThenAllOfThemPresent() {
Users users = repository.findAllUsers();
Map<Character, List<User>> alphabeticalGrouping = users.groupUsersAlphabetically();
List<User> actual = alphabeticalGrouping.get('A');
User[] expected = {
new User(2L, "Auroora", "Oats"),
new User(4L, "Alika", "Capin"),
new User(20L, "Artus", "Rickards"),
new User(27L, "Antonina", "Vivian")};
assertThat(actual).containsExactly(expected);
}
透過這種方式,我們可以隱藏此類方法的實現,消除服務中的混亂,並卸載儲存庫。
4。結論
Spring JPA 允許定制,但有時實現這一點非常簡單。圍繞框架限制的類型建立應用程式可能會影響程式碼的品質甚至應用程式的設計。
使用自訂集合作為傳回類型可能會使設計更加簡單,並且不會因映射和過濾邏輯而變得混亂。使用實體集合的專用包裝器可以進一步改進程式碼。
與往常一樣,本教程中使用的所有程式碼都可以在 GitHub 上找到。