본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Java(자바)

Java 배열로부터 엑셀, csv, 및 일반 text 파일 만들기

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2019. 5. 17.

 

 

프로젝트를 진행하다 보면 80~90% 이상 고객들은 데이터베이스의 자료를 토대로 엑셀 또는 csv, text화 된 파일을 받기를 원한다. Javascript를 활용해서 프론트에서도 작업은 가능 한데..브라우저별 성능에 따라 해당 기능을 지원못하고 한글이 깨지는 경우가 있어서 아직까지는 서버에서 파일을 만든 이후에 전달하는 방법이 조금 더 나은 것 같다.

csv나 text파일은 따로 라이브러리가 필요 없는데, 엑셀을 만드려면 아무래도 poi 라는 라이브러리가 필요하다.

poi를 사용하지 않고 엑셀파일을 만들게 되면 윈도우 환경에서는 해당 파일이 잘 열리는데 맥이나 테블릿에서는 열리지 않을 수 있다.

		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>3.13</version>
		</dependency>		

라이브러리 버전은 프로젝트에 맞게 맞추어 주자.

다음으로, 통합 유틸개념의 클래스이다.

package 생략;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.util.Iterator;

public class FileMaker {
	
	public static enum EncodingType{  //인코딩 타입
		UTF8, EUCKR, Cp1252
	}
	
	public static enum BuildType{  //만드는 형식 타입
		CSV, EXCEL, TXT
	}
	
	private static FileMaker maker;
	private FileMaker(){ }
	
	public static final FileMaker getInstance(){
		synchronized (FileMaker.class) {
			if(maker == null){
				maker = new FileMaker();
			}
		}
		return maker;
	}
	
	//파일 만들기 메소드
	public boolean ListTypeToFile(String path, List<?> list,BuildType type, EncodingType encoding,String... headers) throws Exception{  //headers는 가변인자, 옵셔널개념
		if(type.toString().equals("CSV") || type.toString().equals("TXT")){
			boolean firstJob = __makeCsvOrTxtFile(path, list, encoding, headers);
			if(firstJob){
				unix2dos(path);	
			}
			return true;
		} else if(type.toString().equals("EXCEL")){
			return __makeExcelFile(path, list, encoding, headers);
		} else {
			return false;
		}
		return true;
	}
	
	//엑셀파일용 내부 메소드
	private boolean __makeExcelFile(String path, List<?> list, EncodingType encoding, String... headers) throws Exception{
		FileOutputStream output = null;
		
		int rowNumber = 0;
		HSSFWorkbook workbook = new HSSFWorkbook();
		HSSFSheet sheet = workbook.createSheet();
		HSSFRow row = null;
		
		if(headers != null && headers.length > 0){//헤더설정이 있다면..
			row = sheet.createRow(rowNumber);
			for(int i=0; i < headers.length;i++){
				HSSFCell cell = row.createCell(i);
				cell.setCellValue(headers[i]);				
			}
			rowNumber ++;
		}		
		for(Object T : list){
			row = sheet.createRow(rowNumber);
			if(T instanceof Map){ //맵 형태
				@SuppressWarnings("unchecked")
				Map<Object,Object> map = (Map<Object, Object>)T;
				Iterator<Object> keys = map.keySet().iterator();
				int idx_inner = 0;
		        while( keys.hasNext() ){
		            Object value = map.get(keys.next()); 
		            if(value == null){ continue; }
					row.createCell(idx_inner).setCellValue(value.toString());
					idx_inner++;
		        }
			} else {//일반 vo형태
				BeanInfo info = Introspector.getBeanInfo(T.getClass());
				int idx_inner = 0;
				for(PropertyDescriptor pd : info.getPropertyDescriptors()){
					Method reader = pd.getReadMethod();
					if(reader != null && (!pd.getName().equals("class"))){
						Object value = reader.invoke(T);
						if(value == null){ continue; }
						row.createCell(idx_inner).setCellValue(value.toString());
						idx_inner++;	
					}
				}
			}
			rowNumber++;
		}
		try {
			output = new FileOutputStream(path);
			workbook.write(output);		
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally{
			output.close();
			workbook.close();
		}
		return true;
	}
	
	//csv또는 일반 text만들기 내부 메소드
	private boolean __makeCsvOrTxtFile(String path, List<?> list, EncodingType encoding, String... headers){
		PrintWriter pw = null;
		StringBuilder sb = null;
		try {
			sb = new StringBuilder();
			pw = new PrintWriter(new File(path), encoding.toString());
			if(headers != null && headers.length > 0){
				for(String header : headers){
					sb.append(header);
					if(headers.length > 1){
						sb.append(",");
					}
				}
				sb.append("\n");
			}
			__innerAppenderCsvAndSingle(list, sb);
			pw.println(sb.toString());
			pw.flush();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally{
	        if(pw != null){pw.close();}
	        if(sb != null){sb = null;}
		}
		return true;
	}
	
	//csv 또는 text요청에 따른 데이터 더하기 함수
	private void __innerAppenderCsvAndSingle(List<?> list, StringBuilder sb) throws Exception{
		for(Object T : list){
			if(T instanceof String || T instanceof Integer || T instanceof Boolean || T instanceof Character){ //단일 
				sb.append(T.toString());
				sb.append("\n");
			} else if(T instanceof Map){ //맵 형태
				@SuppressWarnings("unchecked")
				Map<Object,Object> map = (Map<Object, Object>)T;
				Iterator<Object> keys = map.keySet().iterator();
		        while( keys.hasNext() ){
		            Object key = keys.next();
		            Object value = map.get(key); 
		            if(value == null){ continue; }
					sb.append(value);
					sb.append(",");		            
		        }
		        sb.append("\n");
			} else {//일반 vo형태
				BeanInfo info = Introspector.getBeanInfo(T.getClass());
				for(PropertyDescriptor pd : info.getPropertyDescriptors()){
					Method reader = pd.getReadMethod();
					if(reader != null && (!pd.getName().equals("class"))){
						Object value = reader.invoke(T);
						if(value == null){ continue; }
						sb.append(value.toString());
						sb.append(",");
					}
				}
				sb.append("\n");
			}
		}
	}
	
	//유닉스형태 파일을 일반 dos형태 파일로 바꾸는 함수(인코딩방지)
	private void unix2dos(String fname) {
		try {
			File f1 = new File(fname);
			File f2 = new File(fname+".csv");
			BufferedInputStream inp = new BufferedInputStream(new FileInputStream(f1));
			BufferedOutputStream outp = new BufferedOutputStream(new FileOutputStream(f2));
			int c;
			while ((c = inp.read()) > 0) {
				if (c == '\n')
					outp.write('\r');
				outp.write(c);
			}
			inp.close();
			outp.close();
			if (!f1.delete()) {
				System.out.printf("File %s is read-only; converted result can't be saved\n", fname);
				return;
			}
			if (!f2.renameTo(f1))
				System.out.printf("File %s is removed; convert result is saved in %s\n",fname, f2.getAbsolutePath());
		} catch (Exception e) {
			System.out.printf("i/o error while processing %s\n", fname);
		}
	}	
	
	
}

ListTypeToFile메소드에 원하는 데이터를 입력하면 끝이다. List에서의 객체를 제네릭을 활용해서 받기 때문에 어떠한 형식을 쓰든지 간에 편하게 만들 수 있다.

리스트 내부에 map 또는 vo형태의 객체는 1열에 들어간다.

사용 예제를 살펴 보자.

package 생략;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class TestMain {
	

	public static void main(String[] args) {
		
		FileMaker maker = FileMaker.getInstance();

		List<TestVo> list = new ArrayList<TestVo>();
		TestVo vo = new TestVo();  //원하는 vo객체를 만든다. 어떤vo이던지 간에 상관 없다.
		vo.setNumber(10);
		vo.setText("안녕");  

		list.add(vo);
		list.add(vo);
		
		
		try {
			maker.ListTypeToFile("D:/test.xls", list, FileMaker.BuildType.EXCEL, FileMaker.EncodingType.EUCKR);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		List<HashMap<Object, Object>> list2 = new ArrayList<HashMap<Object,Object>>();
		HashMap<Object, Object> item = new HashMap<Object, Object>();
		item.put("1", "1번데이터");
		item.put("2", "2번데이터");
		list2.add(item);

		try {
			maker.ListTypeToFile("D:/test.csv", list2, FileMaker.BuildType.CSV, FileMaker.EncodingType.EUCKR, "1번째","2번째");
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
	}

}

인코딩 타입 뒤의 headers는 가변인자로 엑셀 또는 csv 파일을 만들 때 가장 먼저 데에터를 붙여주는 기능이다.

사실 java 1.8 부터는 옵셔널을 사용 할 수 있는데...현업에서는 아직도 1.7이 대세라..어쩔수 없이 가변인자로 만들었다.

그리고 List에 들어온 데이터가 map형태인지 아니면 일반 vo형태인지를 판단하는 부분에서의 추상화가 덜 되어서 그리 깔끔하지는 않다.

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글