Spring Data JPA Repository 返回 Map
1、概览
Spring JPA 为与数据库交互提供了非常灵活方便的 API。而且,还可以对其进行定制,以返回其他数据结构类型的返回值。
使用 Map
作为 JPA Repository 方法的返回类型有助于在服务和数据库之间创建更直接的交互。本文将带你了解如何在 Spring Data JPA Repository 接口的方法中返回 Map
。
2、手动实现
当框架不提供某些功能时,最明显的解决方法就是自己实现。
2.1、List
可以把返回的 List
映射为 Map
。通过 Stream API 只用一行代码就能实现:
default Map<Long, User> findAllAsMapUsingCollection() {
return findAll().stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
}
2.2、Stream
Repository 接口方法可以直接返回 Stream
:
@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 的 Repository 方法应在事务中调用。所以,直接在 default
方法中添加 @Transactional
注解。
2.3、Streamable
这与之前的方法类似。唯一的变化是返回 Streamable
。先定义一个返回 Streamable
的方法:
@Query("select u from User u")
Streamable<User> findAllAsStreamable();
然后,在 default
方法中对结果进行映射:
default Map<Long, User> findAllAsMapUsingStreamable() {
return findAllAsStreamable().stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
}
3、自定义 Streamable 封装
前面的例子展示了非常简单的解决方案。但是,假设我们有多个不同的操作或数据结构,需要将结果映射到这些操作或数据结构上。在这种情况下,代码中可能到处都是重复的 Mapper 映射代码。
更好的方法可能是创建一个代表实体集合的专用类,并将与集合上的操作相关的所有方法都放在该类中。为此,可以使用 Streamable
。如前所述,Spring JPA Repository 方法可以返回 Streamable
。
创建一个 Users
类来表示 User
对象的集合,继承 Streamable
并为其提供方便的方法:
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();
}
// 自定义方法
}
为了让它与 JPA 配合使用,应该遵循一个简单的约定。首先,应该实现 Streamable
,其次,提供 Spring 能够初始化 Streamable
的方法。初始化部分可以通过使用 Streamable
的 public
构造函数或名称为 of(Streamable<T>)
或 valueOf(Streamable<T>)
的静态工厂来实现。
然后,就可以使用 Users
作为 JPA Repository 方法的返回类型:
@Query("select u from User u")
Users findAllUsers();
现在,可以将 Repository 中保留的方法直接放在 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);
}
通过这种方式,可以隐藏这些方法的实现细节,减少服务层的混乱,并减轻 Repository 的负担。
4、总结
本文介绍了在 Spring Data JPA Repository 接口的方法中返回 Map
的几种方式。
Ref:https://www.baeldung.com/spring-data-return-map-instead-of-list