从1.8版开始,Spring数据项目包含一个有趣的功能 - 通过一个简单的API调用,开发人员可以请求将数据库查询结果作为Java 8流返回。在技术上可行并且由底层数据库技术支持的情况下,结果将逐个流式传输,并且可以使用流操作进行处理。在处理大型数据集时(例如,以特定格式导出大量数据库数据),此技术特别有用,因为除其他外,它可以限制应用程序处理层中的内存消耗。在本文中,我将讨论当Spring数据流与MySQL数据库一起使用时的一些好处(以及陷阱!)。
从数据库中获取和处理大量数据(通过较大的数据集,不适合正在运行的应用程序的内存中)的天真方法通常会导致内存不足。当使用诸如JPA之类的ORM /抽象层时,尤其如此,您无法访问较低级别的工具,这些工具将允许您手动管理从数据库中获取数据的方式。通常,至少对于我通常使用的堆栈--MySQL,Hibernate / JPA和Spring Data--大型查询的整个结果集将完全由MySQL的JDBC驱动程序或之后的上述框架之一获取。如果结果集足够大,这将导致OutOfMemory异常。
解决方案使用分页
让我们专注于一个示例 - 将大型查询的结果导出为CSV文件。当遇到这个问题,当我想留在Spring Data / JPA世界时,我通常会选择寻呼解决方案。查询分解为较小的查询,每个查询返回一页结果,每个查询的大小有限。Spring Data提供了很好的分页/切片功能,使这种方法易于实现。Spring Data的PageRequests被转换为MySQL中的限制/偏移查询。但有一些警告。使用JPA时,实体会缓存在EntityManager的缓存中。需要清除此缓存以使垃圾收集器能够从内存中删除旧的结果对象。
让我们看看分页策略的实际实现在实践中是如何表现的。出于测试目的,我将使用 基于Spring Boot,Spring Data,Hibernate / JPA和MySQL的小型 应用程序。它是一个待办事项列表管理webapp,它具有将所有待办事项下载为CSV文件的功能。待办事项存储在单个MySQL表中。该表已填充了100万条目。这是分页/切片导出功能的代码: @RequestMapping(value = "/todos2.csv", method = RequestMethod.GET)
public void exportTodosCSVSlicing(HttpServletResponse response) {
final int PAGE_SIZE = 1000