들어가기 전에
엑셀 관련 추가적인 내용은 아래 포스팅에서 확인하실 수 있습니다.
https://hojun-dev.tistory.com/entry/JAVA-POI-엑셀-오류
개요
대용량 데이터를 엑셀로 내보낼 때 Java POI에서는 XSSFWorkbook 대신에 SXSSFWorkbook을 사용한다. XSSFWorkbook은 메모리 상에 전체 엑셀 파일을 로딩하기 때문에 파일 크기가 큰 경우 OutOfMemoryError가 발생할 수 있다.
반면에 SXSSFWorkbook은 필요한 데이터만 메모리에 유지하고 나머지 데이터는 디스크에 저장하여 메모리 사용량을 줄인다.
이로 인해 OutOfMemoryError가 발생할 확률이 줄어들고 대용량 데이터를 처리할 수 있다.
프로젝트 오픈 전 QA 과정 중 엑셀 관련 오류가 보고되었는데,
특정한 계정에서 모든 엑셀이 다운로드가 되지 않는 오류가 발생하고 있었다.
확인해 보니 SXSSFWorkbook을 적용한 계정에서 공통적으로 엑셀 다운로드 오류가 발생하고 있었다.
내가 진행하는 프로젝트는 프로젝트 특성상 한셀 대응이 필요한데,
SXSSFWorkbook로 엑셀 생성 시 한셀에서 열리지 않기 때문에 데이터가 많지 않은 계정에서는 XSSFWorkbook을 사용하고 있었고,
데이터가 많은 계정에서는 어쩔 수 없이 한셀을 포기하고 SXSSFWorkbook을 적용해 둔 상태였다.
문제 상황
// 1
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(classPathResource.getInputStream());
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook);
// 2
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();
Sheet sheet = sxssfWorkbook.createSheet();
필자의 경우 프로젝트에서 바탕 엑셀에 값을 추가하는 방식으로 작업해야 하기 때문에 바탕 엑셀의 ClassPathResource를 가져오기 위해 XSSFWorkbook을 생성하고 SXSSFWorkbook에 파라미터로 전달하여 생성하는데,
이때에는 SXSSFWorkbook 생성 시 바로 오류가 발생했다.
SXSSFWorkbook을 XSSFWorkbook을 파라미터로 전달하지 않고 신규로 생성할 수 있는데,
이 경우에는 SXSSFWorkbook 생성까지는 정상적으로 진행되나,
SXSSFWorkbook 생성 이후 엑셀 작성을 위해 createSheet()을 통해 시트를 생성해야 하는데,
여기에서 동일한 오류가 발생한다.
원인 파악
서버 환경 : 네이버 클라우드, ubuntu, docker, nginx
로컬 개발 환경에서는 동일한 문제가 발생하지 않아 직접 서버의 로그를 확인해야 했다.
# 현재 실행 중인 모든 container 목록 확인
docker ps
# my-container 컨테이너에서 최근 20개의 로그 메시지 출력 (-f : 실시간 업데이트)
docker logs --tail 20 -f my-container
발생한 오류는 다음과 같다.
java.lang.NullPointerException
at java.desktop/sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264)
at java.desktop/sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:225)
at java.desktop/sun.awt.FontConfiguration.init(FontConfiguration.java:107)
at java.desktop/sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:719)
at java.desktop/sun.font.SunFontManager$2.run(SunFontManager.java:379)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.desktop/sun.font.SunFontManager.<init>(SunFontManager.java:324)
at java.desktop/sun.awt.FcFontManager.<init>(FcFontManager.java:35)
at java.desktop/sun.awt.X11FontManager.<init>(X11FontManager.java:56)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.desktop/sun.font.FontManagerFactory$1.run(FontManagerFactory.java:84)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.desktop/sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74)
at java.desktop/java.awt.Font.getFont2D(Font.java:497)
at java.desktop/java.awt.Font.canDisplayUpTo(Font.java:2250)
at java.desktop/java.awt.font.TextLayout.singleFont(TextLayout.java:469)
at java.desktop/java.awt.font.TextLayout.<init>(TextLayout.java:530)
at org.apache.poi.ss.util.SheetUtil.getDefaultCharWidth(SheetUtil.java:285)
at org.apache.poi.xssf.streaming.AutoSizeColumnTracker.<init>(AutoSizeColumnTracker.java:117)
at org.apache.poi.xssf.streaming.SXSSFSheet.<init>(SXSSFSheet.java:89)
at org.apache.poi.xssf.streaming.SXSSFWorkbook.createAndRegisterSXSSFSheet(SXSSFWorkbook.java:701)
at org.apache.poi.xssf.streaming.SXSSFWorkbook.<init>(SXSSFWorkbook.java:262)
... 123 more (실행한 부분 및 겹치는 부분 생략)
우리가 코드로 작업한 부분이 아닌,
라이브러리 내 FontConfiguration 관련 부분에서 NullPointerException이 발생한다.
우리는 여기에서 맨 아래 SXSSFWorkbook 관련된 로그에 집중해야 한다.
여기에서 아래 SXSSFWorkbook.<init>은 우리가 SXSSFWorkbook을 XSSFWorkbook 파라미터로 전달해서 실행한 부분이고,
createAndRegisterSXSSFSheet에서 시트를 생성할 때 폰트 관련한 오류가 발생함을 확인할 수 있다.
SXSSFWorkbook을 XSSFWorkbook 파라미터로 전달하지 않는 경우는 createAndRegisterSXSSFSheet가 실행되지 않아 생성까지는 문제없이 진행되나,
추적해보면 createSheet()가 createAndRegisterSXSSFSheet를 호출함을 확인할 수 있고,
그렇기 때문에 해당 부분에서 동일한 오류가 발생함을 확인 수 있다.
해결 방안
image build 시 폰트를 설치할 수 있도록 Dockerfile에 아래와 같은 명령어를 추가한다.
RUN apk update
RUN apk add --no-cache fontconfig ttf-dejavu
참고
- docker container 내에서 서비스가 동작하기 때문에 서버에 폰트를 설치하는 것이 아닌 docker image build시 폰트를 설치할 수 있도록 작업해야 한다.
- 이미지가 apt-get을 포함하지 않을 수 있어 apt-get으로 폰트 설치 시 apt-get not-found가 발생할 수 있다.
apt-get이 이미지에 포함되어 있지 않은 경우 대체적으로 apk 패키지 매니저를 사용할 수 있다.
문제 해결에 도움을 준 사이트
https://joalog.tistory.com/121
'Java & Spring' 카테고리의 다른 글
[JAVA] Calendar로 한국식 월 주차 구하기 (0) | 2023.07.24 |
---|---|
[JAVA] Lombok Builder, SuperBuilder와 Generic 사용하기 (0) | 2023.07.21 |
[Spring] JPA 중복 Insert 방지하기 (0) | 2023.07.18 |
[JAVA] poi에서 엑셀을 다룰 때 발생하는 여러가지 오류 (1) | 2023.04.05 |
[JAVA] JSON Array 형식의 문자열을 List 형식으로 변환하는 방법 (0) | 2023.04.04 |