java - Spring 将 JPA 流式传输到 HTTP
问题描述
我想将大数据(CSV 导出)从 JPA 流式传输到 HTTP 响应。在对 SO 和几个教程进行了多次讨论之后,我得出了以下代码,但是当我运行代码时,我得到一个错误org.hibernate.exception.GenericJDBCException: could not advance using next()
并且SQL Error: 90007 The object is already closed [90007-197]
. 不管我是否放在StreamingResponseBody stream = ...
try/catch 块之外。我怀疑 DB 连接在调用 StreamResponseBody 回调时及时关闭。如何正确构建代码?
public class AdminExportController extends AbstractApiController {
...
@GetMapping(path = "/export/{publicId}")
@ApiOperation("Download file containing export data stored in the database")
@Secured(PERM_EXPORTS_DOWNLOAD)
public ResponseEntity<StreamingResponseBody> streamDataExportFile(@PathVariable("publicId") String publicId, final HttpServletResponse response) {
DataExport dataExport = dataExportService.getFile(publicId);
String filename = dataExport.getType().name() + "_" + LocalDate.now().toString() + ".csv";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(ZenMediaType.TEXT_CSV);
headers.setContentDispositionFormData(filename, filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
StreamingResponseBody stream = null;
try(Stream<String> lines = dataExportService.streamFileContent(dataExport.getType())) {
PrintWriter printer = response.getWriter();
printer.println(dataExport.getCsvHeader());
stream = out -> lines.forEach(line -> {
printer.println(line);
});
printer.flush();
} catch(IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(stream, headers, HttpStatus.OK);
}
...
}
@Service
public class DataExportServiceImpl extends AbstractGwServiceImpl implements DataExportService {
...
@Override
@Transactional(readOnly = true)
public Stream<String> streamFileContent(DataExport.DataExportType type) {
return dataExportLineRepository.streamAllByType(type).map(dataExportLine -> {
String content = dataExportLine.getContent();
entityManager.detach(dataExportLine);
return content;
});
}
...
}
public interface DataExportLineRepository extends MyJpaRepository<DataExportLine, Long> {
@QueryHints(value = {
@QueryHint(name = HINT_FETCH_SIZE, value = "0"),
@QueryHint(name = HINT_CACHEABLE, value = "false"),
@QueryHint(name = READ_ONLY, value = "true")
})
@Query("select del from DataExportLine as del where del.type = :type")
Stream<DataExportLine> streamAllByType(@Param("type") DataExport.DataExportType type);
}
解决方案
在对 SO 进行了更多挖掘之后,我通过直接写入 HTTP 响应来找到这个解决方案。但我仍然很好奇它是否可以与 StreamingResponseBody 一起使用。
public class AdminExportController extends AbstractApiController {
...
@GetMapping(path = VERSION_1 + "/{publicId}")
@ApiOperation("Download file containing export data stored in the database")
@Secured(PERM_EXPORTS_DOWNLOAD)
public void writeDataExportFile(@PathVariable("publicId") String publicId, final HttpServletResponse response) {
DataExport dataExport = dataExportService.getFile(publicId);
String filename = dataExport.getType().name() + "_" + LocalDate.now().toString() + ".csv";
response.setContentType(ZenMediaType.TEXT_CSV_VALUE);
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
try {
PrintWriter writer = response.getWriter();
writer.println(dataExport.getCsvHeader());
writer.flush();
dataExportService.writeFileContent(writer, dataExport.getType());
} catch(IOException e) {
e.printStackTrace();
}
}
...
}
@Service
public class DataExportServiceImpl extends AbstractGwServiceImpl implements DataExportService {
...
@Override
public void writeFileContent(PrintWriter writer, DataExport.DataExportType type) {
dataExportLineRepository.streamAllByType(type).forEach(dataExportLine -> {
String content = dataExportLine.getContent();
entityManager.detach(dataExportLine);
writer.println(content);
writer.flush();
});
}
...
}
public interface DataExportLineRepository extends MyJpaRepository<DataExportLine, Long> {
@QueryHints(value = {
@QueryHint(name = HINT_FETCH_SIZE, value = "0"),
@QueryHint(name = HINT_CACHEABLE, value = "false"),
@QueryHint(name = READ_ONLY, value = "true")
})
@Query("select del from DataExportLine as del where del.type = :type")
Stream<DataExportLine> streamAllByType(@Param("type") DataExport.DataExportType type);
}
推荐阅读
- html - 使用 SVG 作为图像的框架
- ios - 未处理的 JS 异常:ReferenceError:找不到变量:i 【什么是“i”!?!?】反应导航
- java - Docker:无法连接 Spring Boot 和 MYSQL
- java - Java 并行流 - 调用 parallel() 方法的顺序
- regex - 为什么将 abcd.http 重定向到 abcd 的规则如果不在 RewriteCond 和 RewriteRule 中声明相同的正则表达式就不起作用?
- python - 线程模块和多处理模块的比较
- javafx - 校对请求:没有红色下划线的代码,但如果我尝试显示 LineChart,则会收到错误消息。有人可以检查一下吗?
- amazon-web-services - 为 AWS Fargate 配置应用程序端口
- javascript - clone() 生成动态行但具有相同的 id 和 name
- android-databinding - Data Binding 多模块项目中没有这样的方法和字段错误