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

...

 

 

 

 

 

도움이 되었다면

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

 

 

 

 

오랜만에 티스토리에 개발관련 기록을 남기는 것같다.

디비의 값을 엑셀로 다운로드을 수 있도록 해달라는 요청이 있었다.

(요즘은 Android를 파는 중이지만...)

 

요청은 하나의 대상 테이블에 대한 엑셀 다운로드 구현이지만

어차피 개발할 거라면 재사용할 수 있도록 개발했다.

 

 

 

 

 

 

 

 

 

 

0. 의존성 추가 (pom.xml)

 


...
		<!-- excel -->
		<dependency>
		    <groupId>org.apache.poi</groupId>
		    <artifactId>poi</artifactId>
		    <version>4.1.1</version>
		</dependency>
		
		<dependency>
		    <groupId>org.apache.poi</groupId>
		    <artifactId>poi-ooxml</artifactId>
		    <version>4.1.1</version>
		</dependency>
...

 

 

 

 

1. SQL (ExcelMapper.xml)

 

엑셀 다운로드용 쿼리를 모아둘 목적으로 XML 파일을 생성했다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.excel.dao.ExcelMapper">
	<resultMap id="excelListDownloadMap" type="java.util.HashMap" >
    	<result column="CONTENT_NO"     	property="번호"    	jdbcType="INTEGER" />
    	<result column="CONTENT_TITLE"     	property="제목"    	jdbcType="VARCHAR" />
    </resultMap>

    <select id="contentListDownload" resultMap="excelListDownloadMap">
		<![CDATA[
			SELECT CONTENT_NO ,CONTENT_TITLE
			FROM TB_CONTENT
			WHERE CONTENT_NO < 100
		]]>
	</select>
</mapper>

 

 

 

2.  DAO (ExcelMapper.java)

 

현재 개발환경에서는 Mapper 를 사용하기 때문에

interface로 간단하게 DAO를 선언할 수 있다.

반환값을 void, 파라미터를 ResultHandler로 선언하면

Mybatis에서 조회되는 값을 바로 핸들링할 수 있다.

 

(ResultHandler 클래스를 잘 짜놓는다면...ㅎㅎㅎ)

ResultHandler는 너무 길어지니 이어지는 게시글에 자세히 적는걸로!

 

import java.util.Map;

import org.apache.ibatis.session.ResultHandler;
import org.springframework.stereotype.Repository;

@Repository
public interface ExcelMapper {
	public void contentListDownload(ResultHandler<Map<String,Object>> ExcelHander);
}

 

 

 

 

 

 

3. Service (ExcelService.java)

 

난 서비스를 ResultHandler과 DAO의 매핑 공간으로 사용했다.

매핑을 해놓으면 해당 메서드를 호출할때

ExcelHandler에 조회 결과가 매핑되는 걸로 보인다.

난 이 ResultHandler를 통해 다운로드 받을 엑셀 형식을 정의하고

다운로드 로직을 ResultHandler 에 넣어놓았다. (excelHandler.download())

 

import java.io.IOException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.co.excel.dao.ExcelMapper;

@Service
public class ExcelService {
	@Autowired
	private ExcelMapper excelMapper;
	
	ExcelHandler<Map<String,Object>> excelHandler;
	
	public void contentListDownload(
			HttpServletRequest request,
			HttpServletResponse response){
		excelHandler = new ExcelHandler<>(response,"테스트");
		excelMapper.contentListDownload(excelHandler);
		
		try {
			excelHandler.download();
		} catch (IOException e) {
			e.printStackTrace();
			excelHandler.close();
		}
	}
}

 

 

 

 

 

4. Controller (ExcelController.java)

 

View페이지가 별도로 필요없는 RestController를 선언했다. 

엑셀 다운로드시 Response 객체가

필요하기 때문에 파라미터로 선언해놓았다.

Request 객체는 추후에 필요할 듯 싶어서

넣어 놓았을 뿐 당장은 필요없다.

 

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import kr.co.excel.service.ExcelService;

@RestController
public class ExcelController {
	
	@Autowired
	ExcelService excelService;
	
	@RequestMapping(value = "/contentListDown.do")
	public void contentListDownload(
			HttpServletRequest request,
			HttpServletResponse response){
		excelService.contentListDownload(request,response);
	}
}

 

 

 

 

도움이 되었다면

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

 

 

 

 

 


 

[SPRING/Mybatis] POI를 이용한 대용량 데이터 추출 (엑셀 다운) -2

 

[SPRING/Mybatis] POI를 이용한 대용량 데이터 추출 (엑셀 다운) -2

[SPRING/Mybatis] POI를 이용한 대용량 데이터 추출 (엑셀 다운) -1 오랜만에 티스토리에 개발관련 기록을 남기는 것같다. 디비의 값을 엑셀로 다운로드을 수 있도록 해달라는 요청이 있었다. (요즘은 A

kyome.tistory.com


 

 

 

 

자바 개발을 하다보면 날짜 형태에 

맞추어 String으로 저장된 값에 

날짜를 더하거나 빼야할 때가 많다.

 

처음부터 String 형태로 DB에 

저장하는 경우도 많고

 

Controller에서 DB값을 받았을 때, 

이미 SQL로 TO_CHAR( )을 적용한 값을 

받게되는 경우도 있기 때문에 

 

String 으로 Date를 만드는 

방법을 익혀두는게 

마음 편하다.

 

 

 

 


 

" String to Date "

 

 

 

1. DateFormat 선언하기

 

날짜 문자열이 "2020.10.01" 형태로 

들어온다고 가정할 때,

날짜데이터를 잘 구분할 수있도록

형태에 맞추어 DateFormat을

미리 지정해 주어야 한다.

 


DateFormat dateFormat = new SimpleDateFormat ("yyyy.MM.dd"); 

 

 

 

 

 

 

2. Date 형태로 변환하기

 

이제 DateFormat이 제공하는

parse 메소드를 사용해서

Date 인스턴스를 생성한다.

 

파라미터로 String 데이터를 넣으면

그 형태에 맞는 Date 인스턴스를 반환한다.

 


Date endDate = dateFormat.parse("2020.10.01"); 

 

Date를 다룬다고 생각하고

마음편하게 사용하면 된다.

 

 

 

 

 

설정방법

1. log4j.xml파일을 생성(수정)

Spring 으로 세팅하면 기본적으로 log4j를 포함하고 있기 때문에 별도 생성할 필요는 없지만 혹시 없다면 log4j.xml 파일을 생성해야 한다.

Path : src/main/resources/log4j.xml

 

1.log4j.xml 구성

Appender, logger, root 로 구성되어있는 걸로 보인다.

 

Appender는 로그를 찍을 대상이나 어떤 방식으로 찍을 지를 결정하는 설정으로 보인다.

기본 설정값은 console에 찍는 방식으로 되어있고 PatternLayout 클래스를 사용해서 Layout을 잡는다.

 

logger는 Application Loggers라고 주석이 달린 것처럼 package와 같이 영역을 지정하고 해당 영역에서 사용할 logger를 정의하는 태그로 보인다.

하위 파라미터로 level을 받아서 출력할 로그수준을 정한다.

appender-ref 태그를 사용하여 ref 값에 참조할 appender를 입력하여 출력방식을 정할 수 있다.

 

root는 default라고 생각하면 되는 것 같다. 설정하지 않은 logger에 대해서만 root logger를 출력하게 한다. 구성요소는 logger와 유사하다.

**같은 로그가 두번 찍힌다면 Additivity 속성에 대해 확인

 

 

2.설정 파일 기본값

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<logger name="kr.co.ohjooyeo">
		<level value="info" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
			
</log4j:configuration>

 

사용방법

사용할 클래스에 private static final 로거 변수 선언 이렇게 선언을 하면 xml에서 해당 어플리케이션(패키지)의 로거를 생성

1. 선언

Logger의 name을 패키지로 잡았을 경우, 패키지내의 클래스명.class를 파리미터로 선언

private static final Logger logger = LoggerFactory.getLogger(선언한 클래스명.class);

 

Logger의 name을 변수명으로 잡았을 경우, logger의 이름(문자열)을 파리미터로 선언

private static final Logger logger = LoggerFactory.getLogger("test");
<!-- Application Loggers --> <logger name="test"> <level value="info" /> </logger>

Log4j의 구조

1. Logger

  • 로그의 주체 : 로그 파일을 작성하는 클래스
  • 로그레벨을 가짐 (로그문의 레벨과 로거 레벨를 비교하여 로그의 출력여부를 결정)
  • 출력할 메시지를 Appender에 전달

2.Appender

  • 전달된 로그를 어디에 출력할지 결정 ( 콘솔 / 파일 / DB 등)
    • Appender 종류
      • WriterAppender :
        Writer 객체에 로그를 남기는 Appender
      • ConsoleAppender :
        System.out, System.err에 로그를 남기는 Appender

        - 옵션
        Target : System.out / System.err
        Follow : true -> SystemOutStream 에 저장
        activeOption : appender를 활성화

      • FileAppender :
        파일에 로그를 남기는 Appender

        - 옵션
        File : 파일명 Append : 추가 모드 여부 (true/false)
        BufferedIO : 버퍼 사용 여부 (true/false)
        BufferSize : 버퍼 사이즈

        --??--
        Threshold : (AppenderSkelton으로부터 계승)
        ImmediateFlush : (WriteAppender로부터 계승)
        Encoding : (WriteAppender로부터 계승)

      • RollingFileAppender :
        크기에 따라 File명을 변환하며 로그를 남기는 Appender

        - 옵션
        MaxFileSize : 최대 파일 사이즈
        MaxBackupIndex : 로그를 최대 개수
        File, Append, BufferdIO, BufferSize, Threshold, ImmediateFlush, Encoding

      • DailyRollingFileAppender :
        날짜에 따라 File명을 변환하며 로그를 남기는 Appender

        - 옵션 DatePattern : 날짜 형식(yyyy-MM, yyyy-ww,yyyy-MM-dd, yyyy-MM-dd-a, yyyy-MM-dd-HH 등등)

      • RollingFileAppender : 크기에 따라 File명을 변환하며 로그를 남기는 Appender
      • AsyncAppender : Logging Event 발생시 Thread를 생성하여 로그를 남기는 Appender

        -옵션
        triggeringPolicy : 로그 트리거 옵션
        rollingPolicy : 로그 정책 옵션
        org.apache.log4j.rolling.TimeBasedRollingPolicy : 시간 베이스 org.apache.log4j.rolling.SizeBasedTriggeringPolicy : 사이즈 베이스org.apache.log4j.rolling.FilterBasedTriggeringPolicy : 필터 베이스org.apache.log4j.rolling.FixedWindowRollingPolicy : 인덱스 베이스 백업 파일
      • SMTPAppender :
        로그를 이메일로 전달하는 Appender

출처: <https://m.blog.naver.com/PostView.nhn?blogId=youngchanmm&logNo=221029597791&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F>

 

 

3. Layout

  • 어떤 형식으로 출력할지 결정
  • Layout 종류
    • DateLayout
    • HTMLLayout
    • PatternLayout (일반적으로 사용)
      • PatternLayout (org.apache.log4j.PatternLayout) 상세 설명
        • C : 클래스명을 출력
          설정을 추가하여 클래스 이름 또는 특정 패키지 이상만 출력하도록 설정 가능

          - 추가 설정 -
          m : 로그로 전달된 메시지를 출력한다.
          M : 로그를 수행한 메소드명을 출력한다.
          n : 줄 바꿈
          p : 로그 이벤트명 (DEBUG등)
          r : 로그 처리시간 (milliseconds)

        • d : 로그 시간을 출력한다. java.text.SimpleDateFormat에서 적절한 출력 포맷을 지정할 수 있다.
        • F : 파일 이름을 출력한다. 로그 시 수행한 메소드, 라인번호가 함께 출력된다.
        • l (location) : 로깅 이벤트가 발생한 클래스의 풀네임.메서드명(파일명:라인번호) 출력
        • L : 라인 번호만 출력
        • m : 로그로 전달된 메시지 출력
        • M : 로그를 수행한 메소드명 출력
        • n : 줄바꿈
        • p : 로그 이벤트명 (레벨명)
        • r : 로그 처리시간 (milliseconds)
        • T : 로깅 이벤트가 발생한 스레드명

          출처: <https://androphil.tistory.com/420>
      • SimpleLayout
      • XMLLayout

 

 

Log4j 란?
log4j는 프로그램을 작성하는 도중에 로그를 남기기 위해 사용되는 자바기반 로깅 유틸리티이다. 디버그용 도구로 주로 사용되고 있다.
log4j의 최근 버전에 의하면 높은 등급에서 낮은 등급으로의 6개 로그 레벨을 가지고 있다. 설정 파일에 대상별(자바에서는 패키지)로 레벨을 지정이 가능하고 그 등급 이상의 로그만 저장하는 방식이다.
1. 목적
System.out.println() 을 사용하여 로그를 확인할 경우 사용하지 않게 되면 일일이 주석처리를 해야 한다.
로그의 레벨이나 로그문의 레벨에 따라서 로그를 유연하게 출력하여 불필요한 업무를 줄이고 성능을 최적화 할 수 있다.
(그대로 둔다면 프로그램 성능에 영향을 미칠 수 있다.)
2. 종류
6개의 레벨로 구성됨
1.
FATAL : 아주 심각한 에러가 발생한 상태를 나타낸다.
2.
ERROR : 어떠한 요청을 처리하는 중 문제가 발생한 상태를 나타낸다.
3.
WARN : 프로그램의 실행에는 문제가 없지만, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 나타낸다.
4.
INFO : 어떠한 상태변경과 같은 정보성 메시지를 나타낸다.
5.
DEBUG : 개발 시 디버그 용도로 사용하는 메시지를 나타낸다.
6.
TRACE : 디버그 레벨이 너무 광범위한 것을 해결하기 위해서 좀 더 상세한 이벤트를 나타낸다.

+ Recent posts

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