문제 배경
•
커스텀 분석 모델을 응시자 엔티티에서 Enum 필드로 관리하다가, 새로운 커스텀 분석 모델 추가 시마다 재배포 해야하는 문제로 DB 테이블로 이관
•
응시자 엔티티 → 커스텀 모델 엔티티로 ManyToOne 연관관계 참조
문제 상황
•
커스텀 분석 모델이 적용된 채널에서 응시자가 응시 완료 시 커스텀 분석이 수행되지 않음
•
Datadog 확인 시에도 에러 스택 트레이스나 로그가 남아있지 않음
응시 완료 시 역량검사 분석 → 커스텀 모델 분석(CapabilityAnalyzeCompletedEventHandler)
CapabilityAnalyzeCompletedEventHandler 부분 확대
•
Datadog 상으로 커스텀 모델 분석 중 중간에 로직이 끊기고 종료
문제 해결 과정
디버깅
1.
QC나 DEV 서버에서 디버깅하기 위해 로그 추가
•
기존의 돌던 커스텀 모델 분석 로직의 앞 부분도 수행 되지 않음
2.
원인 분석을 위해 로컬 환경 구축 후 IDE 디버그 모드로 테스트
•
엔티티 내부 필드에 LazyInitializationException이 저장되어 있음을 확인
원인 분석
•
기존의 로직이 돌다가 끊기는 이슈
// 비즈니스 로직
if (entity.useCustomModel()) {
return;
}
// 편의 메서드
public boolean useCustomModel() {
return !this.customModel.equals("사용안함");
}
Java
복사
◦
위와 같이 편의 메서드 내부에 엔티티 그래프 탐색이 있고, 해당 연관관계가 지연 로딩으로 설정되어있어 마찬가지로 LazyInitializationException 발생
•
로그가 남지 않는 이유
log.info("이벤트 핸들러 호출 : id = {}", CustomModel.getId());
Java
복사
◦
로그를 남기는 과정에서 이처럼 엔티티 그래프 조회 → LazyInitializationException 발생 → 로그가 남지 않음
•
그렇다면 Exception 관련 스택 트레이스나 로그는 왜 안남는가?
◦
해당 EventHandler가 @Async 애노테이션을 달아두어 비동기로 수행
◦
별도의 스레드에서 수행 중 LazyInitializationException이 발생하더라도 해당 에러에 대한 핸들링 전혀 안됨
•
OSIV는 왜 적용이 안되는가?
◦
OSIV를 켜두면 스프링 부트에서 OpenEntityManagerInViewInterceptor를 등록
◦
이를 통해 EntityManager가 트랜잭션 범위 밖(Open In View)에서도 영속성을 유지
◦
위 과정은 인터셉터를 호출하는 Web Request에서만 동작
◦
내부 로직이나 EventListener는 인터셉터를 호출하지 않기 때문에 OSIV가 동작하지 않음
문제 해결 및 결론
해결 방법은 일반적인 LazyInitializationException의 해결 방법과 동일하다. 트랜잭션을 걸어주거나 필요한 데이터를 미리 조회해두면 된다.
위 코드에서는 외부 API를 호출하여 응답을 받아오는 로직이 포함되어 있어, 트랜잭션을 걸면 해당 응답을 받을 때까지 트랜잭션을 물고 있게 된다는 문제가 있다.
때문에 Fetch Join을 통해서 엔티티 탐색 전 미리 조회를 해두는 방식으로 해결하였다.