Apache POI 流式传输将 XLSX 直接写入 IO

问题描述 投票:0回答:1

我有一个 Web 应用程序,我想在不出现 OOM 的情况下返回包含大量记录的 XLSX。我想将每个页面写入 OutputStream 并在循环中刷新。问题是我遇到了异常。

java.io.IOException: Stream closed
    at java.base/java.io.BufferedWriter.ensureOpen(BufferedWriter.java:132) ~[na:na]
    at java.base/java.io.BufferedWriter.implWrite(BufferedWriter.java:325) ~[na:na]
    at java.base/java.io.BufferedWriter.write(BufferedWriter.java:313) ~[na:na]
    at java.base/java.io.Writer.write(Writer.java:278) ~[na:na]

是否可以逐页将数据写入操作系统,或者唯一可能的方法是将所有内容加载到内存中?

我的代码是:

  public void export(final OutputStream os)  {
        
        try (SXSSFWorkbook workbook = new SXSSFWorkbook(-1)) {

            final SXSSFSheet sheet = workbook.createSheet();


            for (int i = 0; i < 100; i++) {
                final SXSSFRow row = sheet.createRow(i);
                final SXSSFCell cell = row.createCell(0, CellType.STRING);
                cell.setCellValue("Test");

                try {
                    workbook.writeAvoidingTempFiles(os);
                    os.flush();
                } catch (IOException e) {
                    log.info("Error", e);
                }

            }
            workbook.dispose();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

public void export(final HttpServletResponse httpServletResponse) throws IOException {
        
        try (TestBook workbook = new TestBook()) {

            final SXSSFSheet sheet = workbook.createSheet();


            for (int i = 0; i < 100; i++) {
                final SXSSFRow row = sheet.createRow(i);
                final SXSSFCell cell = row.createCell(0, CellType.STRING);
                cell.setCellValue("Test");
                workbook.write(httpServletResponse.getOutputStream());
                httpServletResponse.flushBuffer();
            }
        }
    }

控制器代码:

@GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, path = "/1")
    public ResponseEntity<StreamingResponseBody> download1() {
 return ResponseEntity .ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=\"" +
                       "test" +
                        ".xlsx\"")
                .body(exportService::export);
    }

    @GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, path = "/2")
    public void download2(final HttpServletResponse httpServletResponse) throws IOException {

        httpServletResponse.setHeader("Content-Type", MediaType.APPLICATION_OCTET_STREAM_VALUE);
        httpServletResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=\"" +
                "test2" +
                ".xlsx\"");

        exportService.export(httpServletResponse);
    }

static class TestBook extends SXSSFWorkbook {
        public TestBook() {
            super(-1);
        }
    }

原写方法如下:

    public void write(OutputStream stream) throws IOException {
        flushSheets();

        //Save the template
        File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx");
        boolean deleted;
        try {
            try (FileOutputStream os = new FileOutputStream(tmplFile)) {
                _wb.write(os);
            }

            //Substitute the template entries with the generated sheet data files
            try (
                    ZipSecureFile zf = new ZipSecureFile(tmplFile);
                    ZipFileZipEntrySource source = new ZipFileZipEntrySource(zf)
            ) {
                injectData(source, stream);
            }
        } finally {
            deleted = tmplFile.delete();
        }
        if (!deleted) {
            throw new IOException("Could not delete temporary file after processing: " + tmplFile);
        }
    }
java apache-poi
1个回答
0
投票

您的工作簿是

SXSSFWorkbook
的子类。对于这种类,您不应该在具有相同输出流的循环中调用
write()
,因为,正如您从源代码中看到的,它将流传递给
injectData
,后者将流包装到 ZipOutputStream 中,写入它,然后关闭 ZipOutputStream,这会导致底层流(your流)也被关闭。

循环的第二次迭代然后使用已经关闭的输出流调用

write()

您应该在 for 循环之外调用

write()
并在最后调用它。

© www.soinside.com 2019 - 2024. All rights reserved.