대용량의 데이터를 데이터베이스에서 읽어 Excel로 다운로드 할 때 OOM(Out Of Memory)가 자주 발생한다.
OOM을 해결하기 위해서 Mybatis를 사용하는 경우에는 ResultHandler를 이용하여 각각의 Result에 대해 Excel의 Row를 생성하면 OOM 발생을 방지할 수 있다.
먼저 Mybatis의 ResultHandler를 구현한다.
■ ExcelResultHandler 예제
import java.util.List; import java.util.Map; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import stis.framework.exception.BizException; import stis.framework.util.ExcelStyle; import stis.framework.util.ExcelUtil; import stis.framework.util.NullUtil; public class ExcelResultHandler<T> implements ResultHandler<T> { private static final Logger LOGGER = LoggerFactory.getLogger(ExcelResultHandler.class);
private Workbook workbook = null; private Sheet sheet = null; private int rowNum = 0;
public ExcelResultHandler(Workbook workbook) { this.workbook = new XSSFWorkbook(); try { createSheetTitle(this.workbook, null, null); } catch(Exception ex) {
} } public ExcelResultHandler(Workbook workbook, List<String> headerColumns) { this.workbook = workbook; try { createSheetTitle(this.workbook, null, headerColumns); } catch(Exception ex) {
} }
public ExcelResultHandler(Workbook workbook, String headerName, List<String> headerColumns) { this.workbook = workbook; try { createSheetTitle(this.workbook, headerName, headerColumns); } catch(Exception ex) { LOGGER.error("Exception :: {}", ex.getMessage()); } }
/** * *<pre> * 1.Description: Create excel header and header column * 2.Biz Logic: * 3.Author : LGCNS *</pre> * @param workbook excel workbook * @param headerName excel header name * @param headerColumns excel header column * @throws Exception */ private void createSheetTitle(Workbook workbook, String headerName, List<String> headerColumns) throws Exception { sheet = workbook.createSheet();
/** Create Excel Header Name **/ if (!NullUtil.isNull(headerName)) { Row row = sheet.createRow(rowNum); Cell cell = row.createCell(0); cell.setCellValue(headerName); cell.setCellStyle(ExcelStyle.getHeaderNameStyle(this.workbook)); /** Cell Merge **/ sheet.addMergedRegion(new CellRangeAddress(rowNum, rowNum, 0, headerColumns.size() - 1)); rowNum++; } /** Create excel header columns **/ if (headerColumns != null) { Row row = sheet.createRow(rowNum); int idx = 0; for (String headerColumn : headerColumns) { Cell cell = row.createCell(idx++); cell.setCellStyle(ExcelStyle.getHeaderColumnStyle(this.workbook)); cell.setCellValue(headerColumn); } rowNum++; } } @SuppressWarnings("unchecked") @Override public void handleResult(ResultContext<? extends T> resultContext) { /** Map **/ if (LOGGER.isDebugEnabled()) { LOGGER.debug("Result Count :: {}", resultContext.getResultCount()); LOGGER.debug("Result Class :: {}", resultContext.getClass()); }
if (resultContext.getResultObject() instanceof Map) { try { ExcelUtil.map2Excel(sheet, (Map<String, Object>)resultContext.getResultObject(), ExcelStyle.getDataStyle(workbook), rowNum); } catch (Exception ex) { LOGGER.error("Excel file creation error :: {}", ex.getMessage()); throw new BizException("Excel file creation error"); } rowNum++; } /** Value Object **/ else { try { ExcelUtil.object2Excel(sheet, resultContext.getResultObject(), ExcelStyle.getDataStyle(workbook), rowNum); } catch (Exception ex) { LOGGER.error("Excel file creation error :: {}", ex.getMessage()); throw new BizException("Excel file creation error"); } rowNum++; }
}
} |
ExcelResultHandler는 Mybatis의 ResultHandler의 handleResult 메소드를 구현한다.
ExcelResultHandler는 3개의 생성자를 가지고 있는데 Excel File 생성 시 Title과 Excel Column의 이름을 파라미터를 받아 Excel File에 생성한다.
또한 Workbook은 ExcelResultHandler를 호출하는 객체에서 생성하여 생성자 파라미터를 전달하여야 한다.
handleResult에서는 result의 Generic Type에 따라 Map과 Value Object를 Excel의 sheet에 Row를 생성한다.
다음은 SQL File을 생성한다.
■ SQL 예제
<select id="selUser" parameterType="java.util.HashMap" resultType="java.util.LinkedHashMap"> SELECT ID, NAME, PASSWORD, DESCRIPTION, USE_YN, REG_USER FROM TB_USER WHERE 1 = 1 <if test="ID != null and ID != ''"> AND ID LIKE '%' || #{ID} || '%' </if> </select> |
resultType은 LinkedHashMap을 사용하고 있다. Excel File을 생성하는 경우 Column의 수선가 있기 때문에 LinkedHashMap을 사용하여 SQL 컬럼 순서와 일치한다.
SQL을 호출하기 위한 서비스를 작성한다.
■ Service 예제
@Override public void excelUser(NexacroMapDTO dto, Workbook workbook) throws Exception {
String headerNames = "사용자 정보"; List<String> headerColumns = new ArrayList<String>(); headerColumns.add("사용자 ID"); headerColumns.add("사용자 명"); headerColumns.add("패스워크"); headerColumns.add("설명"); headerColumns.add("사용영부"); headerColumns.add("등록자 ID");
ExcelResultHandler<Map<String, Object>> handler = new ExcelResultHandler<Map<String, Object>>(workbook, headerNames, headerColumns);
getMapper("sbMapper").listToOutUsingResultHandler("user.selUser", handler);
|
Service에서 ExcelResultHandler 객체를 생성하며, SQL의 resultType이Map으로 설정하여 ExcelResultHandler의 GenericType을 Map으로하여 객체를 생성한다.
그리고 Mapper에 해당 SQL ID와 Handler를 넘겨준다.
다음은 Controller 예제이다.
■ Controller 예제
@RequestMapping(value = "/sample/excelResult.do") public ModelAndView excelResult(NexacroMapDTO dto, HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView modelAndView = new ModelAndView();
Workbook workbook = ExcelUtil.createWorkbook(ExcelUtil.ExcelType.XSSF); userService.excelUser(dto, workbook); String fileName = "user.xls"; FileOutputStream fos = null; try { fos = new FileOutputStream(new File( PropertiesUtil.getString("file.upload.root.dir") + File.separator + fileName)); workbook.write(fos);
} catch(Exception ex) { LOGGER.error(ex.getMessage()); } finally { if (fos != null) fos.close(); if (workbook != null) workbook.close(); }
Map<String, Object> fileMap = new HashMap<String, Object>(); fileMap.put("FILE_NM", fileName);
fileMap.put("deleteYn", "Y");
modelAndView.setViewName("fileDownloadView"); modelAndView.addObject("FILE_INFO", fileMap);
return modelAndView; } |
Controller에서 Excel Workbook을 생성하여 서비스 호출 시 해당 Workbook을 파라미터를 전달한다.
서비스가 완료되면 workbook을 파일로 저장한 후 fileDownloadView를 이용하여 임시로 생성된 Excel File을 다운로드 한다.
'Spring Framrwork' 카테고리의 다른 글
Spring framewok Redis session 관리 (0) | 2018.06.08 |
---|---|
Spring framework Future와 AsyncResult 를 이용한 Async 서비스 호출 (0) | 2018.04.23 |
Spring framework Property Reload (0) | 2018.04.13 |
Mybatis include sql (0) | 2018.03.23 |
Spring Framework File Upload with Drag and Drop (0) | 2018.03.16 |