Spring 数据存储库:列表与流
Spring Data repository: list vs stream
在 Spring 数据存储库中定义方法 list
和 stream
时有什么建议?
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.
}
请注意,我在这里不是在询问 Page、Slice - 我很清楚,我找到了他们的描述在 documentation.
我的假设(我错了吗?):
Stream 不会将所有记录加载到 Java Heap。相反,它将 k
条记录加载到堆中并一条一条地处理它们;然后它加载另一个 k
记录等等。
List 会立即将所有记录加载到 Java 堆中。
如果我需要一些后台批处理作业(例如计算分析),我可以使用流操作,因为我不会一次将所有记录加载到堆中。
如果我需要 return 包含所有记录的 REST 响应,无论如何我都需要将它们加载到 RAM 中并将它们序列化到 JSON 中。在这种情况下,一次加载列表是有意义的。
我看到一些开发人员在 return 响应之前将流收集到列表中。
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
是否合理?
Stream
和 Collection
都有对象集合,但是 Collection 及其实现的问题是 Collection
实现拥有内存中的所有元素,实际上 Stream
在 Java8 中被引入来解决这个问题(以及其他一些问题)。想象一下如果 Collection
包含无限数量的元素会发生什么情况,你可以拥有无限数量的元素 Collection
吗?当然你不能,因为无论你的内存有多大,你都会在某个时候摆脱内存异常。但是 Stream 没有这个问题你可以有无限数量的元素 Stream
因为它们不存储在内存中,它们将按需生成。
回到你的问题,想象一下如果你有很多记录在你的第一个查询 findAllByLastName
中有 lastname
会发生什么?当然你会得到 OutOfMemoryError
异常,但是 Stream 解决了这个问题,无论有多少记录满足你的条件,你都不会得到 OutOfMemoryError
异常。
Stream
不在内存中加载对象它按需加载对象,因此它在大型结果查询上表现更好。
所以你的问题的答案:
是的,它按需将元素加载到内存,因此减少了内存消耗量和对数据库的查询调用量。
是的,当您调用该方法时,列表加载所有满足条件的记录。
是的,如果您想遍历满足某些条件的记录并做一些处理工作,您应该使用 Stream one。
这是一个棘手的问题,不知何故不,当您使用 WebFlux
和其他类似的反应式编程方法时,我认为最好选择 Stream
一.
重要说明:如果您说某些开发人员在 return 响应之前将流收集到列表中,他们可以使用 WebFlux 和 return Stream
本身。这是更好的方法。
tl;博士
Collection
VS Stream
的主要区别有以下两个方面:
- 第一个结果的时间 – 客户端代码什么时候看到第一个元素?
- 处理时的资源状态 - 处理流时底层基础设施资源处于什么状态?
使用集合
让我们通过一个例子来说明这一点。假设我们需要从存储库中读取 100k Customer
个实例。您(必须)处理结果的方式给出了上述两个方面的提示。
List<Customer> result = repository.findAllBy();
一旦所有 元素已从基础数据存储中完全读取,客户端代码将收到该列表,而不是在此之前的任何时刻。而且,基础数据库连接 can 已关闭。 IE。例如在 Spring Data JPA 应用程序中,您将看到底层 EntityManager
已关闭并且实体已分离,除非您主动将其保留在更广泛的范围内,例如通过使用 @Transactional
或使用 OpenEntityManagerInViewFilter
注释周围的方法。另外,您不需要主动关闭资源。
使用流
流必须像这样处理:
@Transactional
void someMethod() {
try (Stream result = repository.streamAllBy()) {
// … processing goes here
}
}
使用 Stream
,处理可以在第一个元素(例如数据库中的行)到达并映射后立即开始。 IE。您将能够已经使用元素,而结果集中的其他元素仍在处理中。这也意味着,底层资源需要主动保持开放,因为它们通常绑定到存储库方法调用。请注意 Stream
还必须主动关闭(try-with-resources),因为它绑定了底层资源,我们必须以某种方式向它发出关闭它们的信号。
使用 JPA,如果没有 @Transactional
,Stream
将无法正确处理,因为基础 EntityManager
在方法 return 上关闭。您会看到一些元素已处理,但在处理过程中出现异常。
下游使用
所以虽然理论上您可以使用 Stream
来例如有效地构建 JSON 数组,这会使图片变得非常复杂,因为您需要在编写 all 元素之前保持核心资源打开。这通常意味着编写代码将对象映射到 JSON 并手动将它们写入响应(使用例如 Jackson 的 ObjectMapper
和 HttpServletResponse
.
内存占用
虽然内存占用可能会有所改善,但这主要是因为您希望避免在映射步骤中中间创建集合和附加集合 (ResultSet
-> Customer
- > CustomerDTO
-> JSON 对象)。已经处理的元素不保证从内存中被逐出,因为它们可能由于其他原因而被保留。再次,例如在 JPA 中,您必须保持 EntityManager
打开,因为它控制着资源生命周期,因此所有元素都将绑定到 EntityManager
并一直保留到 all 元素被处理。
在 Spring 数据存储库中定义方法 list
和 stream
时有什么建议?
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.
}
请注意,我在这里不是在询问 Page、Slice - 我很清楚,我找到了他们的描述在 documentation.
我的假设(我错了吗?):
Stream 不会将所有记录加载到 Java Heap。相反,它将
k
条记录加载到堆中并一条一条地处理它们;然后它加载另一个k
记录等等。List 会立即将所有记录加载到 Java 堆中。
如果我需要一些后台批处理作业(例如计算分析),我可以使用流操作,因为我不会一次将所有记录加载到堆中。
如果我需要 return 包含所有记录的 REST 响应,无论如何我都需要将它们加载到 RAM 中并将它们序列化到 JSON 中。在这种情况下,一次加载列表是有意义的。
我看到一些开发人员在 return 响应之前将流收集到列表中。
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
是否合理?
Stream
和 Collection
都有对象集合,但是 Collection 及其实现的问题是 Collection
实现拥有内存中的所有元素,实际上 Stream
在 Java8 中被引入来解决这个问题(以及其他一些问题)。想象一下如果 Collection
包含无限数量的元素会发生什么情况,你可以拥有无限数量的元素 Collection
吗?当然你不能,因为无论你的内存有多大,你都会在某个时候摆脱内存异常。但是 Stream 没有这个问题你可以有无限数量的元素 Stream
因为它们不存储在内存中,它们将按需生成。
回到你的问题,想象一下如果你有很多记录在你的第一个查询 findAllByLastName
中有 lastname
会发生什么?当然你会得到 OutOfMemoryError
异常,但是 Stream 解决了这个问题,无论有多少记录满足你的条件,你都不会得到 OutOfMemoryError
异常。
Stream
不在内存中加载对象它按需加载对象,因此它在大型结果查询上表现更好。
所以你的问题的答案:
是的,它按需将元素加载到内存,因此减少了内存消耗量和对数据库的查询调用量。
是的,当您调用该方法时,列表加载所有满足条件的记录。
是的,如果您想遍历满足某些条件的记录并做一些处理工作,您应该使用 Stream one。
这是一个棘手的问题,不知何故不,当您使用
WebFlux
和其他类似的反应式编程方法时,我认为最好选择Stream
一.
重要说明:如果您说某些开发人员在 return 响应之前将流收集到列表中,他们可以使用 WebFlux 和 return Stream
本身。这是更好的方法。
tl;博士
Collection
VS Stream
的主要区别有以下两个方面:
- 第一个结果的时间 – 客户端代码什么时候看到第一个元素?
- 处理时的资源状态 - 处理流时底层基础设施资源处于什么状态?
使用集合
让我们通过一个例子来说明这一点。假设我们需要从存储库中读取 100k Customer
个实例。您(必须)处理结果的方式给出了上述两个方面的提示。
List<Customer> result = repository.findAllBy();
一旦所有 元素已从基础数据存储中完全读取,客户端代码将收到该列表,而不是在此之前的任何时刻。而且,基础数据库连接 can 已关闭。 IE。例如在 Spring Data JPA 应用程序中,您将看到底层 EntityManager
已关闭并且实体已分离,除非您主动将其保留在更广泛的范围内,例如通过使用 @Transactional
或使用 OpenEntityManagerInViewFilter
注释周围的方法。另外,您不需要主动关闭资源。
使用流
流必须像这样处理:
@Transactional
void someMethod() {
try (Stream result = repository.streamAllBy()) {
// … processing goes here
}
}
使用 Stream
,处理可以在第一个元素(例如数据库中的行)到达并映射后立即开始。 IE。您将能够已经使用元素,而结果集中的其他元素仍在处理中。这也意味着,底层资源需要主动保持开放,因为它们通常绑定到存储库方法调用。请注意 Stream
还必须主动关闭(try-with-resources),因为它绑定了底层资源,我们必须以某种方式向它发出关闭它们的信号。
使用 JPA,如果没有 @Transactional
,Stream
将无法正确处理,因为基础 EntityManager
在方法 return 上关闭。您会看到一些元素已处理,但在处理过程中出现异常。
下游使用
所以虽然理论上您可以使用 Stream
来例如有效地构建 JSON 数组,这会使图片变得非常复杂,因为您需要在编写 all 元素之前保持核心资源打开。这通常意味着编写代码将对象映射到 JSON 并手动将它们写入响应(使用例如 Jackson 的 ObjectMapper
和 HttpServletResponse
.
内存占用
虽然内存占用可能会有所改善,但这主要是因为您希望避免在映射步骤中中间创建集合和附加集合 (ResultSet
-> Customer
- > CustomerDTO
-> JSON 对象)。已经处理的元素不保证从内存中被逐出,因为它们可能由于其他原因而被保留。再次,例如在 JPA 中,您必须保持 EntityManager
打开,因为它控制着资源生命周期,因此所有元素都将绑定到 EntityManager
并一直保留到 all 元素被处理。