[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();
	}
}

...

 

 

 

 

 

도움이 되었다면

로그인이 필요 없는 공감 버튼 꾹 눌러주세요! 

 

 

 

+ Recent posts

"여기"를 클릭하면 광고 제거.