在Spring Data存储库中定义方法list
和steam
时有什么建议?
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中找到了它们的描述。
我的假设(我错了吗?):
Stream不会将所有记录加载到Java Heap中。相反,它将k
记录加载到堆中并逐一处理它们;然后加载另一个k
记录,依此类推。
List确实将所有记录立即加载到Java Heap中。
如果我需要一些后台批处理作业(例如计算分析),则可以使用流操作,因为我不会一次将所有记录加载到堆中。
如果我需要返回包含所有记录的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
时是否有任何示例合理?
答案 0 :(得分:2)
Collection
与Stream
的主要区别在于以下两个方面:
让我们通过一个例子来进行讨论。假设我们需要从存储库中读取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的ObjectMapper
和HttpServletResponse
。
虽然内存占用空间可能会有所改善,但这主要是由于您希望避免在映射步骤中{ResultSet
-> Customer
-> { {1}}-> JSON对象)。不能保证已经处理过的元素 会从内存中逐出,因为它们可能由于其他原因而被保留。同样,例如在JPA中,您必须保持CustomerDTO
处于打开状态,因为它控制着资源生命周期,因此所有元素都将保持与该EntityManager
的绑定,并一直保留到 all 个元素已处理。
答案 1 :(得分:1)
Stream
和Collection
都具有对象集合,但是Collection及其实现的问题在于Collection
实现具有内存中的所有元素,实际上Stream
是Java8中引入的用于解决此问题(以及其他一些问题)的工具。想象一下,如果您有Collection
个元素数量无限,会发生什么?确保您不能这样做,因为无论您的内存多大,都会在某个时候摆脱内存异常。但是Stream并没有这个问题,您可以使用Collection
拥有无限数量的元素,因为它们没有存储在内存中,它们将按需生成。
回到您的问题,想象一下,如果您有很多记录在第一个查询Stream
中有lastname
的情况下会发生什么?当然,您将获得findAllByLastName
例外,但是Stream可以解决此问题,无论有多少条记录符合您的条件,您都不会获得OutOfMemoryError
例外。
OutOfMemoryError
不会在内存中加载对象,而是按需加载对象,因此在大型结果查询中表现更好。
所以您的问题的答案:
是的,它按需将元素加载到内存中,从而减少了内存消耗量和对数据库的查询调用。
是的,列表在调用该方法时加载符合条件的所有记录。
是的,如果您要遍历满足某些条件的记录并执行某些处理工作,则应使用流一。
这是一个棘手的问题,不知道是什么,当您使用Stream
和其他类似方法进行反应式编程时,我认为最好使用WebFlux
。
重要提示:如果您说某些开发人员在返回响应之前将流收集到列表中,则他们可以使用WebFlux提高性能并返回Stream
本身。这是更好的方法。