From ae632932ed19ea243ec11e6bbcee145ae00ee4f4 Mon Sep 17 00:00:00 2001 From: z3rotig4r Date: Tue, 30 Jun 2026 15:26:02 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=97=91=EC=85=80=20=EB=A1=9C=EB=94=A9?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EA=B0=80=20=EB=8B=AB=ED=9E=8C=20?= =?UTF-8?q?Workbook=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loadWorkbook/loadExcelTemplate 계열 메서드가 finally 블록에서 Workbook을 (HSSF의 경우 POIFSFileSystem까지) 닫은 뒤 반환하여, '로드 -> 수정 -> 저장' 워크플로우에서 wb.write() 호출 시 예외가 발생했다. - XSSF: 'document seems to have been closed already' - HSSF: 'directory cannot be null' 반환할 Workbook은 호출자가 수명을 관리하도록 닫지 않고, 메서드가 연 파일 스트림만 닫도록 정리했다. HSSF는 POIFSFileSystem을 닫으면 write가 깨지므로 닫지 않는다(스트림은 생성자에서 전량 읽으므로 닫아도 안전). loadWorkbook(InputStream, XSSFWorkbook)에 이미 적용된 패턴과 동일하다. 로드 후 write 가능 여부를 검증하는 EgovExcelLoadThenWriteTest 추가. --- .../fdl/excel/impl/EgovExcelServiceImpl.java | 6 - .../fdl/excel/EgovExcelLoadThenWriteTest.java | 130 ++++++++++++++++++ 2 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 Foundation/org.egovframe.rte.fdl.excel/src/test/java/org/egovframe/rte/fdl/excel/EgovExcelLoadThenWriteTest.java diff --git a/Foundation/org.egovframe.rte.fdl.excel/src/main/java/org/egovframe/rte/fdl/excel/impl/EgovExcelServiceImpl.java b/Foundation/org.egovframe.rte.fdl.excel/src/main/java/org/egovframe/rte/fdl/excel/impl/EgovExcelServiceImpl.java index eaff43a7..c24c2981 100755 --- a/Foundation/org.egovframe.rte.fdl.excel/src/main/java/org/egovframe/rte/fdl/excel/impl/EgovExcelServiceImpl.java +++ b/Foundation/org.egovframe.rte.fdl.excel/src/main/java/org/egovframe/rte/fdl/excel/impl/EgovExcelServiceImpl.java @@ -155,7 +155,6 @@ public Workbook loadExcelTemplate(String templateName) { wb = new HSSFWorkbook(fileIn); } finally { LOGGER.debug("ExcelServiceImpl loadExcelTemplate End "); - if (wb != null) wb.close(); if (fileIn != null) fileIn.close(); } return wb; @@ -178,7 +177,6 @@ public XSSFWorkbook loadExcelTemplate(String templateName, XSSFWorkbook wb) { wb = new XSSFWorkbook(fileIn); } finally { LOGGER.debug("ExcelServiceImpl loadExcelTemplate(XSSF) End "); - if (wb != null) wb.close(); if (fileIn != null) fileIn.close(); } return wb; @@ -199,7 +197,6 @@ public Workbook loadWorkbook(String filepath) { fileIn = new FileInputStream(filepath); wb = loadWorkbook(fileIn); } finally { - if (wb != null) wb.close(); if (fileIn != null) fileIn.close(); } return wb; @@ -219,7 +216,6 @@ public XSSFWorkbook loadWorkbook(String filepath, XSSFWorkbook wb) { fileIn = new FileInputStream(filepath); wb = loadWorkbook(fileIn, wb); } finally { - if (wb != null) wb.close(); if (fileIn != null) fileIn.close(); } return wb; @@ -241,8 +237,6 @@ public Workbook loadWorkbook(InputStream fileIn) { fs = new POIFSFileSystem(fileIn); wb = new HSSFWorkbook(fs); } finally { - if (wb != null) wb.close(); - if (fs != null) fs.close(); if (fileIn != null) fileIn.close(); } return wb; diff --git a/Foundation/org.egovframe.rte.fdl.excel/src/test/java/org/egovframe/rte/fdl/excel/EgovExcelLoadThenWriteTest.java b/Foundation/org.egovframe.rte.fdl.excel/src/test/java/org/egovframe/rte/fdl/excel/EgovExcelLoadThenWriteTest.java new file mode 100644 index 00000000..d848bfa8 --- /dev/null +++ b/Foundation/org.egovframe.rte.fdl.excel/src/test/java/org/egovframe/rte/fdl/excel/EgovExcelLoadThenWriteTest.java @@ -0,0 +1,130 @@ +package org.egovframe.rte.fdl.excel; + +import jakarta.annotation.Resource; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.egovframe.rte.fdl.excel.config.ExcelTestConfig; +import org.egovframe.rte.fdl.filehandling.EgovFileUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 엑셀 파일을 로딩한 뒤 그대로 다시 write() 할 수 있는지 검증한다. + *

+ * loadWorkbook/loadExcelTemplate 계열 메서드가 반환 직전에 Workbook(및 HSSF의 경우 + * POIFSFileSystem)을 닫아 버리면, "로드 → 수정 → 저장" 워크플로우에서 write() 시 + * 예외가 발생한다(XSSF: "document seems to have been closed already", HSSF: + * "directory cannot be null"). 본 테스트는 로딩된 Workbook이 write() 가능한 상태로 + * 반환되는지 확인하여 해당 회귀를 방지한다. + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = ExcelTestConfig.class) +public class EgovExcelLoadThenWriteTest { + + private final String fileLocation = "testdata"; + + @Resource(name = "excelService") + private EgovExcelService excelService; + + /** + * XSSF(.xlsx) : loadWorkbook(String, XSSFWorkbook)로 로딩한 뒤 write() 가능해야 한다. + */ + @Test + public void testLoadWorkbookThenWriteXssf() throws IOException { + String path = fileLocation + "/loadThenWrite.xlsx"; + if (EgovFileUtil.isExistsFile(path)) { + EgovFileUtil.delete(new File(path)); + } + + XSSFWorkbook src = new XSSFWorkbook(); + Sheet sheet = src.createSheet("data"); + sheet.createRow(0).createCell(0).setCellValue("hello"); + excelService.createWorkbook(src, path); + + Workbook loaded = excelService.loadWorkbook(path, new XSSFWorkbook()); + Cell cell = loaded.getSheetAt(0).getRow(0).getCell(0); + assertEquals("hello", cell.getStringCellValue()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + loaded.write(baos); + assertTrue(baos.size() > 0, "loaded XSSF workbook must be writable"); + } + + /** + * XSSF(.xlsx) : loadExcelTemplate(String, XSSFWorkbook)로 로딩한 뒤 write() 가능해야 한다. + */ + @Test + public void testLoadExcelTemplateThenWriteXssf() throws IOException { + String path = fileLocation + "/loadTemplateThenWrite.xlsx"; + if (EgovFileUtil.isExistsFile(path)) { + EgovFileUtil.delete(new File(path)); + } + + XSSFWorkbook src = new XSSFWorkbook(); + src.createSheet("data").createRow(0).createCell(0).setCellValue("tmpl"); + excelService.createWorkbook(src, path); + + XSSFWorkbook loaded = excelService.loadExcelTemplate(path, new XSSFWorkbook()); + assertEquals("tmpl", loaded.getSheetAt(0).getRow(0).getCell(0).getStringCellValue()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + loaded.write(baos); + assertTrue(baos.size() > 0, "loaded XSSF template must be writable"); + } + + /** + * HSSF(.xls) : loadWorkbook(String)으로 로딩한 뒤 write() 가능해야 한다. + */ + @Test + public void testLoadWorkbookThenWriteHssf() throws IOException { + String path = fileLocation + "/loadThenWrite.xls"; + if (EgovFileUtil.isExistsFile(path)) { + EgovFileUtil.delete(new File(path)); + } + + HSSFWorkbook src = new HSSFWorkbook(); + src.createSheet("data").createRow(0).createCell(0).setCellValue("hssf"); + excelService.createWorkbook(src, path); + + Workbook loaded = excelService.loadWorkbook(path); + assertEquals("hssf", loaded.getSheetAt(0).getRow(0).getCell(0).getStringCellValue()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + loaded.write(baos); + assertTrue(baos.size() > 0, "loaded HSSF workbook must be writable"); + } + + /** + * HSSF(.xls) : loadExcelTemplate(String)으로 로딩한 뒤 write() 가능해야 한다. + */ + @Test + public void testLoadExcelTemplateThenWriteHssf() throws IOException { + String path = fileLocation + "/loadTemplateThenWrite.xls"; + if (EgovFileUtil.isExistsFile(path)) { + EgovFileUtil.delete(new File(path)); + } + + HSSFWorkbook src = new HSSFWorkbook(); + src.createSheet("data").createRow(0).createCell(0).setCellValue("hssftmpl"); + excelService.createWorkbook(src, path); + + Workbook loaded = excelService.loadExcelTemplate(path); + assertEquals("hssftmpl", loaded.getSheetAt(0).getRow(0).getCell(0).getStringCellValue()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + loaded.write(baos); + assertTrue(baos.size() > 0, "loaded HSSF template must be writable"); + } +}