티스토리 뷰
반응형
필자는 업무를 하면서 게임 회사 백오피스쪽을 지원하고 있는데 엑셀 다운로드하는 기능에 대한 요청이 자주왔기에 엑셀 모듈을 만들기로 했습니다.
필자는 Annotation 과 Java Reflection을 이용해 DTO에 어노테이션을 적용하여 손쉽게 Excel 다운로드 하는 모듈을 개발할 것 입니다.
혹시 해당 코드를 구현할 시간이 없다! 나는 그냥 라이브러리 추가해서 빨리 구현해야 한다는 분은 아래 링크로 들어가시면 빠르게 라이브러리를 추가해서 사용하실 수 있습니다.
https://russell-seo.tistory.com/8
Excel 다운로드 모듈 만들기
1. Apache POI 라이브러리 추가
- Apache POI 라이브러리를 활용하여 개발
- 먼저 Apache POI 라이브러리를 Dependency에 추가한다. gradle은 쉽게 구글링을 통해 찾을 수 있을 것이다.
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
2. 본격적으로 Java 코드로 보여드리며 설명
- 보통 엑셀을 보면 Sheet, Row, Cell, CellStyle이 존재합니다.
- POI 라이브러리에서 WorkBook을 열어서 Sheet, Row를 한줄씩 읽어가면서 각 Cell에 데이터를 Write해주기만 하면된다.
- 말은 쉽지만 막상 코드로 구현할려면 처음에 머뭇거리게 된다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelColumn {
String HeaderName() default "";
ExportColumn.DataType DataType() default DataType.none;
}
1. 먼저 DTO 클래스의 필드에 붙일 어노테이션을 정의한다.
- HeaderName -> 엑셀 다운로드를 하면 첫번째 Row에 보통 각 행을 설명하는 HeaderName이 오게 하는 필드이다.
- DataType -> 각 Cell이 숫자인지 문자인지등을 판단해 Cell에 데이터 타입을 설정하기 위한 필드이다.
2. 이제 Excel 데이터를 Export하게 해줄 class를 만든다.
public class ExcelExport<T> {
private static final int ROW_START_INDEX = 0;
private static final int COLUMN_START_INDEX = 0;
private SXSSFWorkbook wb;
private SXSSFSheet sheet;
public ExcelExport(List<T> data, Class<T> type) {
this.wb = new SXSSFWorkbook();
renderExcel(data, type);
}
private void renderExcel(List<T> data, Class<T> objectType){
sheet = wb.createSheet();
createHeaders(sheet, ROW_START_INDEX, COLUMN_START_INDEX, objectType);
if(data.isEmpty()) return;
int rowIndex = ROW_START_INDEX + 1;
for (T datum : data) {
createBody(datum, rowIndex++, COLUMN_START_INDEX, objectType);
}
}
- 먼저 Generic 타입으로 클래스를 생성한다. 제네릭 타입으로 받는 이유는 하나의 DTO가 아닌 여러 타입을 모두 다 받을 수 있게 하기 위해 제네릭 타입으로 선언하였다.
- 생성자로 ExcelExport 를 생성할때 `Excel 에 들어갈 데이터와` `DTO 클래스`를 파라미터로 받는다. 그리고 Excel Workbook을 생성하고 renderExcel 이라는 메소드에 파라미터를 넘긴다.
- renderExcel 메소드는 Sheet를 생성하고 Header와 각 Row의 값을 Write 한다. 아래 코드에서 두 개의 메소드를 볼 수 있다.
3. 첫번째 Row의 Header 값, 각 Row의 Body 값을 Reflection 으로 넣어준다.
/**
* Set Excel Header
* Get from Class Annotation @HeaderName
*/
private void createHeaders(SXSSFSheet sheet, int rowStartIndex, int columnStartIndex, Class<T> objectType) {
SXSSFRow row = sheet.createRow(rowStartIndex);
for (Field declaredField : objectType.getDeclaredFields()) {
SXSSFCell cell = row.createCell(columnStartIndex++);
ExcelColumn annotation = declaredField.getAnnotation(ExcelColumn.class);
cell.setCellValue(annotation.HeaderName());
}
}
- createHeader 메소드는 간단하다. 첫번째 Row를 생성하고 인자로 받은 DTO에서 Reflection으로 Field 값을 가져온다.
- 그리고 각 필드에 선언된 어노테이션인 ExcelColumn의 필드인 HeaderName을 Cell에 추가한다.
/**
* Set Excel Data Row
*/
private void createBody(Object obj, int i, int columnStartIndex, Class<T> objectType) {
SXSSFRow row = sheet.createRow(i);
for (Field declaredField : objectType.getDeclaredFields()) {
SXSSFCell cell = row.createCell(columnStartIndex++);
Field field = getField(obj.getClass(), declaredField.getName());
ExcelColumn annotation = declaredField.getAnnotation(ExcelColumn.class);
field.setAccessible(true);
try {
Object value = field.get(obj);
addCellValueByType(value, cell, annotation.DataType());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
private Field getField(Class<?> aClass, String name) {
try {
return aClass.getDeclaredField(name);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
- createBody 메소드도 Header와 동일한 방법으로 루프를 돈다.
- row를 생성하고 DTO의 필드를 가져온다.
- ExcelColumn의 데이터타입 어노테이션을 가져와서 Cell 타입과 각 Cell에 넣을 Data를 addCellValueByType 메소드로 넘긴다.
private void addCellValueByType(Object value, SXSSFCell cell, DataType dataType){
if(DataType.currency.equals(dataType)){
cell.setCellType(CellType.NUMERIC);
cell.setCellValue(NumberFormat.getCurrencyInstance(new Locale("en", "US")).format(value));
}else if(DataType.date.equals(dataType)){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
cell.setCellValue(sdf.format(value));
}else if(DataType.datetime.equals(dataType)){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
cell.setCellValue(sdf.format(value));
}else if(DataType.percentage.equals(dataType)){
Double d = Double.valueOf(value.toString()) * 100;
cell.setCellValue(Math.floor(d * 100)/100 + "%");
}else{
cell.setCellValue(value == null ? "" : value.toString());
}
}
- 각 Cell 에 넣을 데이터와 DataType을 받아와서 DataType 마다 해당 Format으로 cell.setCellValue로 각 Cell에 데이터를 쓴다.
public void write(HttpServletResponse res, String fileName) throws IOException {
res.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8") + ";");
wb.write(res.getOutputStream());
wb.close();
res.getOutputStream().close();
}
- 마지막으로 HttpServletResponse 를 Controller 에서 받아서 Excel 파일의 Header와 FileName을 설정하고 OutputStream으로 파일을 내보낸다.
- WorkBook을 열었기 때문에 close() 해주는 것을 잊으면 안된다.
3. 이제 엑셀 파일을 다운로드하는 모듈을 통해 실습해보자.
@Data
public class PidChangeDownloadReq {
@ExcelColumn(HeaderName = "PID", DataType = DataType.none)
private String pid;
@ExcelColumn(HeaderName = "유저 ID", DataType = DataType.number)
private Long userId;
@ExcelColumn(HeaderName = "로그인 ID", DataType = DataType.none)
private String paramString
}
- DTO 클래스를 정의한다.
- 해당 필드 위에 위에서 우리가 정의한 @ExcelColumn 어노테이션을 선언한다.
@RequestMapping("/test")
public void excelExportTest(@RequestBody List<PidChangeDownloadReq> req, HttpServletResponse res){
ExcelExport excelExport = new ExcelExport<>(req, PidChangeDownloadReq.Class);
excelExport.write(res, "파일이름");
}
- 데이터를 넣을 파라미터로 해당 req 와 DTO.Class를 넘겨준다.
- 파일이름을 write 메소드로 넘겨준다.
- 해당 파일에 대한 내용은 대외비라서 공개하기 어렵기 때문에 생략하겠다.
반응형
'Java' 카테고리의 다른 글
[Java]비동기 CompletableFuture 로 안전하게 (0) | 2023.04.12 |
---|---|
[Java] Optional 로 null 처리 (0) | 2023.03.21 |
Java 메모리구조(Heap, Stack) (0) | 2023.03.17 |
Java Excel 다운로드 라이브러리(JitPack) (1) | 2023.02.27 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 비동기
- java
- DispatcherServlet
- 다운로드
- Spring Security
- TCP
- spring boot
- 인덱스
- 데이터베이스
- db
- GIS
- oauth2
- 공간쿼리
- thread
- 네트워크
- mysql
- 쓰레드
- lock
- 논블로킹
- Index
- 영속성 컨텍스트
- github
- spring mvc
- 스프링
- R-Tree
- jpa
- database
- spring
- jenkins
- Excel
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함