Excel 입출력의 주요 활용 사례
1. 데이터 입력 자동화
수작업 데이터 입력을 줄이고, 대량의 정보를 빠르게 시스템에 반영할 수 있습니다.
2. 결과 데이터 추출
분석 결과나 통계 정보를 엑셀 파일로 내보내어 보고서 작성 또는 외부 공유에 사용할 수 있습니다.
3. 시스템 간 데이터 연계
서로 다른 플랫폼이나 데이터베이스를 가진 시스템 사이에서 데이터 이관이나 연동을 위한 매개체로 활용됩니다.
EasyExcel 소개 및 장점
기존 Apache POI나 JXL과 같은 Java 기반 Excel 처리 라이브러리는 간편함은 있으나, 대용량 파일 처리 시 메모리 과다 사용으로 인해 OutOfMemoryError(OOM)가 발생하기 쉽습니다. 특히 .xlsx 형식(Office 2007 이상)은 압축 구조를 가지며, 전체 해제 과정에서 메모리에 모든 내용을 적재하므로 문제를 더욱 악화시킵니다.
Alibaba에서 개발한 EasyExcel은 이러한 문제를 해결하기 위해 설계되었습니다. 내부적으로 스트림 기반 처리 방식(SAX 모델)을 사용하여 한 번에 전체 파일을 로드하지 않고, **한 행씩 읽고 즉시 파싱**함으로써 메모리 사용량을 극도로 낮춥니다. 예를 들어, 3MB의 Excel 파일도 POI 기반 SAX 파싱 시 약 100MB의 힙 메모리를 필요로 할 수 있지만, EasyExcel은 이를 몇 MB 수준으로 줄일 수 있으며, 심지어 수백만 행의 데이터라도 안정적인 처리가 가능합니다.
주요 특징
- 대용량 Excel 파일에서도 안정적인 성능 제공
- OOM 위험 최소화 – Row 단위 스트리밍 처리
- 사용자 정의 리스너(
AnalysisEventListener)를 통해 실시간 데이터 처리 지원 - 애노테이션 기반 매핑으로 객체와 셀 간 연결이 직관적
공식 리소스
- 공식 사이트: https://easyexcel.opensource.alibaba.com/
- GitHub 저장소: https://github.com/alibaba/easyexcel
- Gitee 미러: https://gitee.com/easyexcel/easyexcel
Spring Boot 프로젝트에 적용하기
1. Maven 의존성 추가
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.3</version>
</dependency>
<!-- XML 처리를 위한 필수 라이브러리 -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
</dependency>
※ easypoi 관련 의존성은 본격적인 EasyExcel 사용에는 불필요하므로 제거 권장
2. 유틸리티 클래스 작성
다음은 Excel 입출력을 위한 재사용 가능한 유틸 클래스입니다.
@Slf4j
public class ExcelHandler {
/**
* 데이터 목록을 Excel 파일로 다운로드 전송
*
* @param data 출력할 데이터 리스트
* @param clazz 데이터 타입 (매핑용)
* @param sheetName 시트 이름
* @param fileName 파일명 (확장자 생략)
* @param response HTTP 응답 객체
* @param <T> 제네릭 타입
*/
public static <T> void exportToExcel(List<T> data, Class<T> clazz,
String sheetName, String fileName,
HttpServletResponse response) {
try {
// 응답 헤더 설정
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
// EasyExcel을 이용한 스트림 출력
EasyExcel.write(response.getOutputStream(), clazz)
.sheet(sheetName)
.doWrite(data);
} catch (Exception e) {
log.error("Excel 내보내기 실패: ", e);
sendErrorResponse(response, "파일 생성 중 오류 발생");
}
}
private static void sendErrorResponse(HttpServletResponse response, String message) {
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\":\"" + message + "\"}");
} catch (IOException ignored) {}
}
/**
* 업로드된 Excel 파일을 객체 리스트로 변환
*
* @param file MultipartFile 객체
* @param tClass 변환 대상 클래스 타입
* @param <T> 제네릭 타입
* @return 변환된 객체 리스트
*/
public static <T> List<T> importFromExcel(MultipartFile file, Class<T> tClass) {
List<T> result = new ArrayList<>();
try (InputStream inputStream = file.getInputStream()) {
EasyExcel.read(inputStream, tClass, new PageReadListener<T>(dataList ->
result.addAll(dataList)))
.sheet()
.doReadSync();
} catch (IOException e) {
log.error("Excel 읽기 실패: ", e);
}
return result;
}
}
3. 데이터 매핑용 모델 클래스 예시
public class UserRecord {
\@ExcelProperty(value = "사용자 이름", index = 0)
private String name;
\@ExcelProperty(value = "이메일 주소", index = 1)
private String email;
\@ExcelProperty(value = "가입 일자", index = 2)
private Date registerDate;
// Getter 및 Setter
}
4. 컨트롤러에서의 사용 예시
내보내기 API:
@GetMapping("/export")
public void exportData(HttpServletResponse response) {
List<UserRecord> users = userService.getAllUsers();
ExcelHandler.exportToExcel(users, UserRecord.class, "회원목록", "users_2024", response);
}
입력 API:
@PostMapping("/import")
public ResponseEntity<String> importData(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("파일이 없습니다.");
}
List<UserRecord> records = ExcelHandler.importFromExcel(file, UserRecord.class);
userService.batchSave(records);
return ResponseEntity.ok("성공적으로 " + records.size() + "건 입력 완료");
}
5. 주의사항
- 대용량 데이터 입력 시에는
PageReadListener를 활용하거나, 청크 단위로 처리하여 DB 부하를 분산해야 합니다. - 엑셀 헤더 매칭은
@ExcelProperty의 value 또는 index 기반으로 수행되며, 오타 유무를 반드시 검증해야 합니다. - 날짜, 숫자 등 형식이 고정된 셀은 적절한 포맷 어노테이션(ex.
@DateTimeFormat)을 함께 사용하는 것이 좋습니다.