프로젝트를 진행하다 보면 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형태인지를 판단하는 부분에서의 추상화가 덜 되어서 그리 깔끔하지는 않다.
반응형
'Java(자바)' 카테고리의 다른 글
Java poi 스타일 적용 및 적용이 되지 않는 경우 (10) | 2019.05.28 |
---|---|
Java 1.8 Optional 그리고 stream (0) | 2019.05.20 |
파일종류, Dos파일 / Unix파일 (0) | 2019.04.25 |
자바 기본 인코딩 (with 파일 입출력) (0) | 2019.04.25 |
Java 반응형 프로그래밍 (0) | 2019.04.25 |
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)
댓글