현재 서비스중인 레거시의 화면은 miplatform
으로 개발되어 있으며 Grid
Excel Export
의 경우 화면에 출력된 Grid
의 데이터와 스타일 그대로 Excel
로 출력해준다.
우선 화면에서의 Grid
출력은 현재 사용 예정인 ag-grid
에서 제공하는 export
를 이용하기로 하였으며 나는 화면 Grid
출력이외의 서버에서의 export
를 구현하기로 하였다. 반드시 ag-grid
를 사용하지 않는 화면도 존재 할것이므로 최대한 많은 기능을 포함시키는것을 목표로 했다.
이번에도 역시 다른 멋진 개발자분들의 경험과 시행착오들을 빌리기 위해 어김없이 구글을 통해 여러 적용기를 확인하였고 우아한블로그에 최태현 개발자님의 작성하신 글(아 엑셀다운로드 개발,,, 쉽고 빠르게 하고 싶다 (feat. 엑셀 다운로드 모듈 개발기))이 많은 도움이 되었다.
먼저 생각한 것은 내가 작성한 코드이기 때문에 나는 해당 사용방법에 납득이 되지만 다른 개발자분들의 입장에서 최대한 납득이 될 수 있는 구조의 Excel Export
를 제공하고 싶었다. 하지만 내가 다른 개발자의 입징이라면 납득하지 못할 것이다.
레거시의 Excel Export
는 위에서 이야기한것 처럼 Grid
를 그대로 Excel Export
하기 때문에 정말 형태가 어마무시한 경우가 존재하였다.
Excel
라이브러리는 고민없이 poi
를 선택하였으며 위에 소개한 최태현 개발자님의 글(아 엑셀다운로드 개발,,, 쉽고 빠르게 하고 싶다 (feat. 엑셀 다운로드 모듈 개발기))에서 처럼 클래스의 필드에 어노테이션을 적용하여 Excel
구성을 하기로 결정하였다.
레거시의 Excel
형태는 어마무시하여 최태현 개발자님이 작성하신 글의 내용보다 어노테이션에 포함시켜야 할 정보들이 더욱 많았다.
우선 헤더의 행과 데이터의 행이 반드시 일치 하지 않은 경우가 존재했다. 이러한 이유로 @ExcelHeader
와 @ExcelBody
를 구분하였다. 덕분에 한 필드에 두개의 어노테이션을 추가하는 선택을 하였다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelHeader {
String headerName();
int rowIndex() default 0;
int colIndex();
int colSpan() default 0;
int rowSpan() default 0;
HeaderStyle headerStyle() default @HeaderStyle;
}
@ExcelHeader
의 경우에는 칼럼명, 인덱스를 필수값으로 입력을 받으며 필요에 따라 행병합[rowSpan]
, 열병합[colSpan]
, 스타일[headerStyle]
이 존재하는 경우 정의하도록 하였다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelBody {
int rowIndex() default 0;
int colIndex();
int colSpan() default 0;
int rowSpan() default 0;
int width() default 8;
boolean rowGroup() default false;
BodyStyle bodyStyle() default @BodyStyle;
}
@ExcelBody
는 칼럼의 인덱스만 필수값이며 필요에 따라 행병합[rowspan]
, 열병합[colSpan]
, 넓이[width]
, 그룹여부[rowGroup]
, 스타일[bodyStyle]
이 존재하는 경우 정의하도록 하였다.
헤더 행과 데이터 행의 기본 적용되는 스타일의 다른부분이 존재하며 설정되는 속성도 다른 부분이 존재하여 스타일 또한 @HeaderStyle
과 @BodyStyle
로 구분하여 정의하였다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HeaderStyle {
Background background() default @Background;
int fontSize() default 11;
HorizontalAlignment horizontalAlignment() default HorizontalAlignment.CENTER;
VerticalAlignment verticalAlignment() default VerticalAlignment.CENTER;
}
@HeaderStyle
속성 자체가 Optional한 속성이기 때문에 필수 값은 존재하지 않으며 배경[background]
, 글씨크기[fontSize]
, 수평정렬[horizontalAlignment]
, 수직정렬[verticalAlignment]
를 지정할 수 있도록 하였다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BodyStyle {
Background background() default @Background;
int fontSize() default 11;
HorizontalAlignment horizontalAlignment() default HorizontalAlignment.GENERAL;
VerticalAlignment verticalAlignment()default VerticalAlignment.CENTER;
String numberFormat() default "";
String dateFormat() default "YYYY-MM-DD HH:mm:ss";
}
@BodyStyle
도 마찬가지로 Optional한 속성이기 때문에 필수 값은 존재하지 않으며 배경[background]
, 글씨크기[fontSize]
, 수평정렬[horizontalAlignment]
, 수직정렬[verticalAlignment]
, 숫자표현형태[numberFormat]
, 날짜표현형태[dateFormat]
를 지정할 수 있도록 하였다.
이러한 annotation
을 통해 Excel
를 Export
하기 위한 Class
파일을 다음과 같은 예시처럼 정의하면 된다.
@Setter
@Getter
@NoArgsConstructor
public class Sample1 {
@ExcelHeader(headerName = "반", colIndex = 0, rowIndex = 0, rowSpan = 1, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 0,colIndex = 0,rowGroup = true, bodyStyle = @BodyStyle(horizontalAlignment = HorizontalAlignment.CENTER))
private String seq;
@ExcelHeader(headerName = "이름", colIndex = 1, rowIndex = 0, rowSpan = 1, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 0, colIndex = 1, rowSpan = 1)
private String name;
@ExcelHeader(headerName = "국어/수학", colIndex = 2, colSpan = 1, rowIndex = 0, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
private String koreanMathHeader;
@ExcelBody(rowIndex = 0, colIndex = 2)
private String korean;
@ExcelBody(rowIndex = 0, colIndex = 3)
private String math;
@ExcelHeader(headerName = "영어", colIndex = 2, rowIndex = 1, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 1, colIndex = 2)
private String english;
@ExcelHeader(headerName = "역사", colIndex = 3, rowIndex = 1, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 1, colIndex = 3)
private String history;
@ExcelHeader(headerName = "생일", colIndex = 4, rowIndex = 0, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 0, colIndex = 4, bodyStyle = @BodyStyle(dateFormat = "YYYY-MM-DD"), width = 12)
private LocalDateTime birthDay;
@ExcelHeader(headerName = "금액", colIndex = 4, rowIndex = 1, headerStyle = @HeaderStyle(background = @Background("#ECEFF3")))
@ExcelBody(rowIndex = 1, colIndex = 4, bodyStyle = @BodyStyle(numberFormat = "#,000원"))
private BigDecimal money;
public Sample1(String seq, String name, String korean, String math, String english, String history, LocalDateTime birthDay, BigDecimal money) {
this.seq = seq;
this.name = name;
this.korean = korean;
this.math = math;
this.english = english;
this.history = history;
this.birthDay = birthDay;
this.money = money;
}
}
주의 해야할 내용으로는 행병합 또는 열병합이 있을경우 index
를 계산하여 다음 행, 열의 index
를 지정해야 한다.
위와 같이 정의한 후 다음 글에서 다룰 해당 클래스에 명시한 @ExcelHeader
, @ExcelBody
의 정보를 통해 Apache-poi
를 이용하여 Excel
을 출력한 결과는 다음과 같다.
생각보다 글의 내용이 많아져서 내용을 나누어 작성하려고 한다. 현재 첫 번째 글에서는 Excel
을 나태나기 위한 정보? 메타데이터? 구성하는 방식에 어노테이션을 사용한 내용을 주로 다루었으며 다음 글에서 해당 어노테이션들을 이용하여 전달받은 데이터와 어노테이션의 정보를 통해 실제 엑셀을 출력하는 로직을 다루도록 하겠다.