Spring数据存储库:列表与流

时间:2020-07-27 12:57:35

标签: java spring spring-data

在Spring Data存储库中定义方法liststeam时有什么建议?

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.
}

请注意,这里我不是要询问页面切片-对我来说它们很清楚,我在documentation中找到了它们的描述。


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

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

  2. List确实将所有记录立即加载到Java Heap中。

  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);
    }
}

在这种情况下,我看不到Stream的任何优势,使用list将产生相同的最终结果。

那么使用list时是否有任何示例合理?

2 个答案:

答案 0 :(得分:2)

tl; dr

CollectionStream的主要区别在于以下两个方面:

  1. 获得第一个结果的时间 –客户代码何时看到第一个元素?
  2. 处理中资源的状态-处理流时底层基础结构资源处于什么状态?

使用集合

让我们通过一个例子来进行讨论。假设我们需要从存储库中读取10万个Customer实例。您(必须)处理结果的方式会提示上述两个方面。

List<Customer> result = repository.findAllBy();

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

使用流

必须像这样处理流:

@Transactional
void someMethod() {

  try (Stream result = repository.streamAllBy()) {
    // … processing goes here
  }
}

使用Stream,处理过程可以在第一个元素(例如数据库中的行)到达并被映射后立即开始。即您将可以使用元素,而结果集中的其他元素仍在处理中。这也意味着,底层资源需要积极地保持开放状态,因为它们通常与存储库方法调用绑定在一起。请注意,Stream在绑定底层资源时还必须积极关闭(尝试资源),我们必须以某种方式通知它关闭它们。

使用JPA,如果没有@Transactional,则Stream将无法正确处理,因为基础EntityManager会在方法返回时关闭。您会看到一些已处理的元素,但在处理的中间出现了异常。

下游使用情况

因此,从理论上讲,您可以使用Stream例如有效地建立JSON数组,这将使图片变得非常复杂,因为您需要保持核心资源开放,直到您编写 all 元素为止。这通常意味着编写代码以将对象映射到JSON并将其手动写入响应(例如使用Jackson的ObjectMapperHttpServletResponse

内存占用量

虽然内存占用空间可能会有所改善,但这主要是由于您希望避免在映射步骤中{ResultSet-> Customer-> { {1}}-> JSON对象)。不能保证已经处理过的元素 会从内存中逐出,因为它们可能由于其他原因而被保留。同样,例如在JPA中,您必须保持CustomerDTO处于打开状态,因为它控制着资源生命周期,因此所有元素都将保持与该EntityManager的绑定,并一直保留到 all 个元素已处理。

答案 1 :(得分:1)

StreamCollection都具有对象集合,但是Collection及其实现的问题在于Collection实现具有内存中的所有元素,实际上Stream是Java8中引入的用于解决此问题(以及其他一些问题)的工具。想象一下,如果您有Collection个元素数量无限,会发生什么?确保您不能这样做,因为无论您的内存多大,都会在某个时候摆脱内存异常。但是Stream并没有这个问题,您可以使用Collection拥有无限数量的元素,因为它们没有存储在内存中,它们将按需生成。

回到您的问题,想象一下,如果您有很多记录在第一个查询Stream中有lastname的情况下会发生什么?当然,您将获得findAllByLastName例外,但是Stream可以解决此问题,无论有多少条记录符合您的条件,您都不会获得OutOfMemoryError例外。 OutOfMemoryError不会在内存中加载对象,而是按需加载对象,因此在大型结果查询中表现更好。

所以您的问题的答案:

  1. 是的,它按需将元素加载到内存中,从而减少了内存消耗量和对数据库的查询调用。

  2. 是的,列表在调用该方法时加载符合条件的所有记录。

  3. 是的,如果您要遍历满足某些条件的记录并执行某些处理工作,则应使用流一。

  4. 这是一个棘手的问题,不知道是什么,当您使用Stream和其他类似方法进行反应式编程时,我认为最好使用WebFlux

重要提示:如果您说某些开发人员在返回响应之前将流收集到列表中,则他们可以使用WebFlux提高性能并返回Stream本身。这是更好的方法。