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 的方法。初始化部分可以通过使用 Streamablepublic 构造函数或名称为 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