[SPRING/Mybatis] POI를 이용한 대용량 데이터 추출 (엑셀 다운) -1
오랜만에 티스토리에 개발관련 기록을 남기는 것같다. 디비의 값을 엑셀로 다운로드을 수 있도록 해달라는 요청이 있었다. (요즘은 Android를 파는 중이지만...) 요청은 하나의 대상 테이블에 대한
kyome.tistory.com
이전 게시글에 이어서,
이제 ResultHandler 를 작성한다.
0. ResultHandler (ExcelHandler.java)
ResultHandler는 handleResult라는 메서드를
Override 하기만 하면 쉽게 사용할 수 있다.
handleResult는 데이터가 들어오는대로
건마다 호출되는 걸로 보인다.
1. 변수 및 생성자 선언
ResultHandler는 Generic으로 선언할 수 있고
내가 만들 ExcelHandler는 Map으로 받아져야하기 때문에
Generic 변수 T는 Map을 상속받는 변수야한다는 조건을 걸었다.
ResultHandler를 통해 다운로드를 받을 수 있도록 할 계획이기 때문에
생성자로 Response를 받으며 파일명이나
컬럼 순서를 받을 수 있도록 생성자를 추가했다.
| import ... |
| |
| public class ExcelHandler<T extends Map<String,Object>> implements ResultHandler<T> { |
| public static final Logger LOGGER = LoggerFactory.getLogger(ExcelHandler.class); |
| |
| private T result; |
| private String title; |
| private String filename; |
| private SXSSFWorkbook workbook; |
| private SXSSFSheet sheet; |
| private HttpServletResponse response; |
| private ResultContext<? extends T> context; |
| private List<String> columnTitleList; |
| private int rownum; |
| |
| final int TITLE = 0; |
| final int BODY = 1; |
| |
| |
| private ExcelHandler() { |
| super(); |
| rownum = 0; |
| } |
| |
| public ExcelHandler(HttpServletResponse response, String filename) { |
| this(); |
| this.response = response; |
| this.title = filename; |
| try { |
| this.filename = URLEncoder.encode(filename.replace(" ", "_"),"UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| e.printStackTrace(); |
| }; |
| workbook = new SXSSFWorkbook(10000); |
| sheet = workbook.createSheet(title); |
| } |
| |
| public ExcelHandler(HttpServletResponse response, |
| String filename,List<String> orderedColumnTitleList) { |
| this(response,filename); |
| this.columnTitleList = orderedColumnTitleList; |
| } |
| |
| ... |
2. 엑셀에 쓰기 메서드 구현
DB 에서 조회한 결과의 Column이 몇 개이고 이름이 무엇이든 상관없이
독립적으로 실행이 되어야 재사용이 가능해지기 때문에
엑셀에 입력하는 기능에만 집중했고
나머지는 파라미터로 받도록 했다.
| private void write(int type,int currentRow,CellStyle style) { |
| |
| if (columnTitleList == null) { |
| columnTitleList = new ArrayList<String>(context.getResultObject().keySet()); |
| } |
| SXSSFRow row = sheet.createRow(currentRow); |
| |
| if(columnTitleList.size() == 0 ) { |
| return; |
| } |
| |
| for(int i = 0 ; i < columnTitleList.size() ; i++) { |
| SXSSFCell cell = row.createCell(i); |
| String tempValue = ""; |
| |
| switch (type) { |
| case TITLE: |
| tempValue = columnTitleList.get(i); |
| break; |
| case BODY: |
| if(context.getResultObject().containsKey(columnTitleList.get(i))) { |
| tempValue = context.getResultObject().get(columnTitleList.get(i)).toString(); |
| } |
| |
| break; |
| } |
| |
| if(style != null) { |
| cell.setCellStyle(style); |
| } |
| cell.setCellValue(tempValue); |
| } |
| } |
3. handleResult Override
데이터가(한개의 행이) 호출 될 때마다 workbook에 입력해주고
현재의 행을 나타내는 변수를 ++ 해준다.
헤더전용 CellStyle 과 내용전용 CellStyle을
미리 선언해두어서 write에 마지막 파라미터로 넣어주면
필요에 따라 셀스타일을 변경하면서 입력할 수도 있다.
| ... |
| |
| @Override |
| public void handleResult(ResultContext<? extends T> resultContext) { |
| if(resultContext.getResultObject() == null) { |
| return; |
| } |
| this.context = resultContext; |
| result = context.getResultObject(); |
| |
| |
| Font headerFont = workbook.createFont(); |
| headerFont.setFontName("맑은 고딕"); |
| headerFont.setBold(true); |
| |
| CellStyle headerStyle = workbook.createCellStyle(); |
| headerStyle.setBorderTop(BorderStyle.THIN); |
| headerStyle.setBorderBottom(BorderStyle.THIN); |
| headerStyle.setBorderLeft(BorderStyle.THIN); |
| headerStyle.setBorderRight(BorderStyle.THIN); |
| |
| headerStyle.setAlignment(HorizontalAlignment.CENTER); |
| headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); |
| headerStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex()); |
| headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); |
| headerStyle.setFont(headerFont); |
| |
| Font bodyFont = workbook.createFont(); |
| bodyFont.setFontName("맑은 고딕"); |
| |
| CellStyle bodyStyle= workbook.createCellStyle(); |
| bodyStyle.setBorderTop(BorderStyle.THIN); |
| bodyStyle.setBorderBottom(BorderStyle.THIN); |
| bodyStyle.setBorderLeft(BorderStyle.THIN); |
| bodyStyle.setBorderRight(BorderStyle.THIN); |
| |
| bodyStyle.setAlignment(HorizontalAlignment.LEFT); |
| bodyStyle.setFont(bodyFont); |
| |
| if(rownum == 0 ) { |
| write(TITLE, rownum,headerStyle); |
| write(BODY, rownum+1,bodyStyle); |
| }else { |
| write(BODY, rownum+1,bodyStyle); |
| } |
| rownum++; |
| } |
| |
| ... |
4. 엑셀 다운로드
생성자로 Response 객체를 받는 이유는 여기에 있다.
Response 객체의 헤더에 첨부파일을 넣어서
OutStream으로 파일을 내보낸다.
파일 처리는 항상 그렇듯 예외처리를 성실하게 해줘야하고
종료시에 close 를 호출해줘야한다.
| ... |
| |
| public void download() throws IOException{ |
| LOGGER.debug("## start excel download : "+filename); |
| response.setHeader("Content-Disposition", "attachment; filename=" |
| + filename.replaceAll(" ", "_") + ".xlsx;"); |
| response.setCharacterEncoding(Constants.ENCODING); |
| |
| ServletOutputStream stream = response.getOutputStream(); |
| OutputStream out = new BufferedOutputStream(stream); |
| |
| try { |
| response.resetBuffer(); |
| response.setBufferSize(1024 * 4); |
| workbook.write(out); |
| |
| } catch (Exception e) { |
| out.flush(); |
| out.close(); |
| stream.close(); |
| } finally { |
| out.flush(); |
| out.close(); |
| stream.close(); |
| } |
| |
| if (workbook != null) { |
| try { |
| workbook.dispose(); |
| } catch (Exception e) { |
| workbook.close(); |
| } finally { |
| workbook.close(); |
| } |
| } |
| workbook.close(); |
| } |
| |
| public void close() { |
| workbook.dispose(); |
| try { |
| workbook.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| ... |
도움이 되었다면
로그인이 필요 없는 공감 버튼 꾹 눌러주세요!