所以我遇到了这个问题,我需要将巨大的 SQL 结果(数百万行)作为文件返回给客户端。 为了解决这些问题,我将它们分成两部分:
技术堆栈:SpringMVC、Java11、PostgreSQL、JdbcTemplate、Hibernate。
为了解决第一点,我使用了jdbcTemplate,因为提到的一些资源hibernate的scrollableResults对于PostgreSQL来说内存不友好。 JdbcTemplate.queryForStream() 似乎是正确的选择。
String query = "select name from usersTable limit 1000000";
jdbcTemplate.setFetchSize(10_000);
Stream<String> revs = jdbcTemplate.queryForStream(query,
(resultSet, rowNum) ->
resultSet.getString("name"));
return ResponseEntity
.status(HttpStatus.OK)
.contentType(MediaType.valueOf(MediaType.MULTIPART_FORM_DATA_VALUE))
.body(new InputStreamResource(IOUtils.toInputStream(String.join("\n", revs.collect(Collectors.toList())))));
资源:https://jvmaware.com/streaming-json-response/
为了解决第二点,我使用的是 ResponseEntitiy,这是几乎每个人都推荐的。
API 似乎按预期很好地发回了文件。
但是,当我查看 jconsole 时,我看到堆内存不断上升,直到文件被发送,并且在文件发送到浏览器时,出现了更大的峰值。我预计该流会被 InputStream 隐式刷新,并且不会导致任何高内存使用问题。
有人可以指出做这两件事的正确方法吗?
如有任何帮助,我们将不胜感激。
谢谢
我尝试了
JdbcTemplate.queryForStream()
,但还没有机会测试它是否有效,因为我现在专注于从服务层传输大结果。
我尝试写入 HttpServletResponse.getOutputStream() 但同样的内存峰值。
我假设一旦写入输出流就必须刷新它。但是出现了这个错误。
2024-02-29 20:14:23,775 ERROR [com.sample.controller.exception.ApplicationExceptionHandler] (default task-2) Exception Occurred : org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.sample.service.impl.UserServiceImpl$$Lambda$858/0x0000000801578840] with preset Content-Type 'multipart/form-data'
相同的代码:
String query = "select name from usersTable limit 1000000";
jdbcTemplate.setFetchSize(10_000);
Stream<String> revs = jdbcTemplate.queryForStream(query,
(resultSet, rowNum) ->
resultSet.getString("name"));
StreamingResponseBody responseBody = httpResponseOutputStream ->
revs.forEachOrdered(row -> {
try {
IOUtils.copy(IOUtils.toInputStream(row), httpResponseOutputStream);
httpResponseOutputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
return ResponseEntity
.status(HttpStatus.OK)
.cacheControl(CacheControl.noCache())
.contentType(MediaType.valueOf(MediaType.MULTIPART_FORM_DATA_VALUE))
.body(responseBody);
不确定我是否缺少任何配置。
这个问题的解决方案是使用老式 JDBC。
Connection
中获得一个DataSource
。FetchSize
)。Statement
/PreparedStatement
/...FetchSize
设置为应用程序性能最佳的值(50,000 对我来说有效)。ResultSet
。ResultSet
并继续写入每一行(如果需要的话,在任何转换之后。将其转换为 String
或任何 byte[]
able 对象)ServletOutputStream
的HttpServletResponse
。就是这样!!
注意:
try with resources
轻松管理资源。干杯!