Oracle JDBC + GraalVM Native 빌드 환경에서
Missing character set id 846 is not loaded at image build time
이 오류가 발생하는 배경과 해결 과정을 상세히 설명합니다.
오류 메시지
이 오류는 GraalVM Native로 빌드된 애플리케이션을 실행할 때
Oracle DB 연결 초기화 단계에서 발생합니다.
특히 com.zaxxer.hikari.HikariPool 초기화 중에 터지는 경우가 많습니다.
🎯 오류 배경: 왜 발생할까?
1. Oracle JDBC의 내부 동작
- Oracle JDBC는 DB 연결 시, Oracle 서버에서 정의한 NLS_CHARACTERSET 값을 받아 처리합니다.
- 서버의 문자셋이 KO16MSWIN949일 경우, Oracle JDBC는 내부적으로 charset ID 846에 해당하는 Java charset 클래스를 로딩합니다.
- 예) sun.nio.cs.ext.MS949, IBM949
2. GraalVM Native의 제한 사항
- GraalVM Native는 클래스/리소스/리플렉션/SPI를 명시적으로 등록하지 않으면 실행 시점에 사용할 수 없습니다.
- Oracle JDBC는 runtime 시점에 해당 charset 클래스를 ServiceLoader와 리플렉션으로 동적으로 로딩하려고 합니다.
- 그런데 이 클래스들이 native image에 포함되지 않으면, 결국 위와 같은 에러가 발생합니다.
🔍 핵심 원인 요약
Oracle JDBC가 charset 846(MS949) 매핑 시도 | JDK 내부 sun.nio.cs.ext.MS949 필요 |
GraalVM native image에는 해당 클래스가 없음 | 기본적으로 제외됨 |
리플렉션/리소스/SPI 등록 누락 | GraalVM Native에서 실행 실패 |
✅ 해결 방법
이 에러를 해결하려면 GraalVM 빌드시 charset 관련 리소스를 명시적으로 포함시켜야 합니다.
단순한 리플렉션 힌트만으로는 부족하며, 빌드 타임 초기화와 NativeImageFeature 활용이 핵심입니다.
✅ 1. native-image.properties 작성
경로: src/main/resources/META-INF/native-image/your.group/your-app/native-image.properties
Args = -H:+ReportExceptionStackTraces \
--features=com.example.CharsetFeature \
--initialize-at-build-time=oracle.jdbc.CharacterSet&CharacterSetNameMapHolder,oracle.i18n.text.converter.CharacterConverter1Byte
✅ 2. NativeImageFeature 구현 클래스 작성
public class CharsetFeature implements Feature {
@Override
public void duringAnalysis(DuringAnalysisAccess access) {
try {
Class<?> clazz = Class.forName("oracle.sql.CharacterSet");
access.requireAnalysisIteration();
File[] fields = new Field[]{clazz.getDeclaredField("K016MSWIN949_CHARSET")}; //초기화 안되는 charset
Method method = class.getDeclaredMethod("make", Integer.TYPE);
method.setAccessible(true);
RuntimeReflection.register(Class.forName("sun.nio.cs.ext.MS949"));
RuntimeReflection.register(Class.forName("sun.nio.cs.ext.IBM949"));
RuntimeReflection.register(Class.forName("sun.nio.cs.ext.ExtendedCharsets"));
} catch (ClassNotFoundException e) {
System.out.println("Charset class not found: " + e.getMessage());
}
}
}
🔥 핵심은 duringAnalysis() 안에서 직접 리플렉션 등록을 강제하는 것
✅ 3. orai18n 의존성 추가 (Oracle JDBC charset 확장 지원)
✅ 5. GraalVM SDK 추가 (Feature 구현용)
- Native image 실행 시 T4CConnection 생성 정상 작동
- Oracle JDBC 연결 성공
- HikariPool 초기화 완료
- charset id 846 에러 완전 해결
🏁 관련 링크
Maven Central Repository Search
search.maven.org
GraalVM의 duringAnalysis 메서드와 Oracle JDBC의 CharacterSet 초기화
1. duringAnalysis 메서드의 역할
GraalVM의 Feature 인터페이스는 네이티브 이미지 빌드 과정에서 다양한 단계에 개입할 수 있는 메서드를 제공합니다. 그 중 duringAnalysis 메서드는 정적 분석이 진행되는 동안 호출되며, 이 시점에서 애플리케이션의 메타데이터를 활용하여 특정 클래스나 메서드를 등록하거나 조작할 수 있습니다. 이를 통해 런타임에 필요한 요소들을 미리 준비할 수 있습니다.
2. Oracle JDBC의 CharacterSet 초기화 필요성
Oracle JDBC 드라이버는 데이터베이스와의 통신 시 문자셋을 처리하기 위해 내부적으로 CharacterSet 클래스를 사용합니다. 특히, 확장 문자셋(예: KO16MSWIN949)의 경우, 해당 문자셋을 지원하는 클래스를 빌드 타임에 초기화해야 합니다. 이를 통해 런타임 시 이미 초기화된 문자셋을 이미지 힙에서 가져와 사용할 수 있습니다. 기본 문자셋은 Oracle JDBC 라이브러리에서 빌드 타임에 초기화되도록 지원하지만, 확장 문자셋은 별도의 설정이 필요합니다.
3. duringAnalysis 메서드를 활용한 CharacterSet 초기화 방법
duringAnalysis 메서드 내에서 Oracle JDBC의 CharacterSet 클래스를 초기화하려면 다음과 같은 단계를 수행합니다:
- CharacterSet 클래스 로드: Class.forName("oracle.sql.CharacterSet")을 사용하여 클래스를 로드합니다.
- 특정 필드 접근: 클래스 내에서 K016MSWIN949_CHARSET과 같은 특정 문자셋 필드를 getDeclaredField 메서드를 통해 가져옵니다.
- 필드 접근 허용: setAccessible(true)를 호출하여 해당 필드에 접근할 수 있도록 설정합니다.
이를 통해 해당 문자셋 필드를 빌드 타임에 초기화하여 런타임 시 문제없이 사용할 수 있습니다.
4. 관련 문서 및 참고 자료
- GraalVM SDK Java API Reference - Feature
- GraalVM Points-to Analysis Guide
- GraalVM GitHub - Feature.java 소스
'장애 개선 > Error' 카테고리의 다른 글
GraalVM Native 실행 시 clone3 때문에 발생한 에러 정리 (0) | 2025.04.01 |
---|---|
JPA 더티체킹 문제와 스프링배치 대량 데이터 업데이트 해결 사례 (0) | 2025.01.14 |
graalvm 빌드 시 에러 |Logging system failed to initialize using configuration from 'null' (2) | 2024.12.23 |
UnsatisfiedLinkError: already loaded in another classloader 에러 (0) | 2023.04.05 |
ClassNotFoundException: javax.xml.bind.DataTypeConverter (0) | 2023.01.18 |