티스토리 뷰

Java

Java로 Excel 다운로드 쉽게하기(@어노테이션으로)

개발도 운동만큼 2023. 2. 19. 23:27
반응형

필자는 업무를 하면서 게임 회사 백오피스쪽을 지원하고 있는데 엑셀 다운로드하는 기능에 대한 요청이 자주왔기에 엑셀 모듈을 만들기로  했습니다.

 

필자는 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 메소드로 넘겨준다.
  • 해당 파일에 대한 내용은 대외비라서 공개하기 어렵기 때문에 생략하겠다.

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함