Spring数据存储库:列表与流

Yan*_*ski 19 java spring spring-data

何时定义方法list以及stream在 Spring 数据存储库中定义什么建议?

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-streaming

例子:

interface UserRepository extends Repository<User, Long> {

  List<User> findAllByLastName(String lastName);

  Stream<User> streamAllByFirstName(String firstName);                    
         
  // Other methods defined.
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里我不是在询问PageSlice - 它们对我来说很清楚,并且我在文档中找到了它们的描述。


我的假设(我错了吗?):

  1. Stream 不会将所有记录加载到 Java 堆中。相反,它将k记录加载到堆中并一一处理它们;然后它加载另一个k记录等等。

  2. List 会立即将所有记录加载到 Java 堆中。

  3. 如果我需要一些后台批处理作业(例如计算分析),我可以使用流操作,因为我不会立即将所有记录加载到堆中。

  4. 如果我需要返回包含所有记录的 REST 响应,我无论如何都需要将它们加载到 RAM 中并将它们序列化为 JSON。在这种情况下,立即加载列表是有意义的。


我看到一些开发人员在返回响应之前将流收集到列表中。

class UserController {

    public ResponseEntity<List<User>> getUsers() {
        return new ResponseEntity(
                repository.streamByFirstName()
                        // OK, for mapper - it is nice syntactic sugar. 
                        // Let's imagine there is not map for now...
                        // .map(someMapper)  
                       .collect(Collectors.toList()), 
                HttpStatus.OK);
    }
}
Run Code Online (Sandbox Code Playgroud)

对于这种情况,我没有看到 Stream 的任何优势,使用list会产生相同的最终结果。

那么有什么例子说明使用list是合理的吗?

Oli*_*ohm 26

太长了;博士

\n

CollectionVS的主要区别Stream有以下两个方面:

\n
    \n
  1. 第一个结果的时间\xe2\x80\x93 客户端代码何时看到第一个元素?
  2. \n
  3. 处理时的资源状态- 处理流时底层基础设施资源处于什么状态?
  4. \n
\n

使用集合

\n

让我们通过一个例子来讨论这个问题。假设我们需要Customer从存储库读取 100k 个实例。您(必须)处理结果的方式暗示了上述两个方面。

\n
List<Customer> result = repository.findAllBy();\n
Run Code Online (Sandbox Code Playgroud)\n

一旦从底层数据存储中完全读取了所有元素,客户端代码就会接收该列表,而不是在此之前的任何时刻。而且,底层数据库连接也可能已关闭。例如,在 Spring Data JPA 应用程序中,您将看到底层EntityManager被关闭,实体被分离,除非您主动将其保持在更广泛的范围内,例如通过@Transactional使用OpenEntityManagerInViewFilter. 另外,您不需要主动关闭资源。

\n

使用流

\n

流必须像这样处理:

\n
@Transactional\nvoid someMethod() {\n\n  try (Stream result = repository.streamAllBy()) {\n    // \xe2\x80\xa6 processing goes here\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

使用Stream,一旦第一个元素(例如数据库中的行)到达并被映射,处理就可以开始。即,您将能够在仍在处理结果集的其他元素的同时使用元素。这也意味着,底层资源需要主动保持开放,因为它们通常绑定到存储库方法调用。请注意,当它Stream绑定底层资源时,它也必须主动关闭(尝试使用资源),并且我们必须以某种方式通知它关闭它们。

\n

使用 JPA,如果没有 JPA @TransactionalStream将无法正确处理,因为底层EntityManager在方法返回时关闭。您会看到一些元素已处理,但在处理过程中出现异常。

\n

下游使用

\n

因此,虽然理论上您可以使用例如Stream高效地构建 JSON 数组,但它会使情况变得非常复杂,因为您需要保持核心资源开放,直到编写完所有元素。这通常意味着编写代码将对象映射到 JSON 并手动将它们写入响应(例如使用 Jackson 的ObjectMapperHttpServletResponse.

\n

内存占用

\n

虽然内存占用可能会有所改善,但这主要是因为您希望避免在映射步骤(ResultSet-> Customer-> CustomerDTO-> JSON 对象)中中间创建集合和附加集合。已处理的元素不能保证从内存中逐出,因为它们可能因其他原因而被保留。同样,例如在 JPA 中,您必须保持开放,EntityManager因为它控制资源生命周期,因此所有元素将保持绑定到该开放状态EntityManager,并保留到所有元素都被处理为止。

\n

  • 关于持有实体的EntityManager,可以使用`entityManager.detach(obj);`来释放实体(参考:https://medium.com/predictly-on-tech/spring-data-jpa-batching-使用流-af456ea611fc) (2认同)