Post

Hibernate 의존성 버그

문제 상황

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
@IdClass(AId.class)
public class A {  
  
    @Id  
    @ManyToOne
    private User user;
  
    @Id  
    @ManyToOne
    private User follower;  
  
    static public class AId implements Serializable {  
        Long user; // User의 ID는 Long이다.  
        Long follower;  
    }  
}

문제를 알아보기 앞서 엔티티 A는 위와 같은 형태로 다중 FK를 묶어 PK로 사용한다.

모든 컴포넌트를 띄운뒤 인수 테스트를 진행하던 중 테스트에 빨간불이 들어왔다. 이유는 서버가 INTERNAL SERVER ERROR를 응답하는 것이었는데, 해당 메시지는 서비스에서 처리되지 않은 에러일 경우에만 발생한다. 즉, 무언가 처리되지 않은 예외가 발생했다.

특이 사항으론 실제 로컬 서버를 띄워 요청을 하면 예외가 발생하지 않는 것이 있었다. 로컬 서버와 인수 테스트 환경의 차이는 DB 종류가 유일하다. 로컬서버는 MariaDB를 테스트 환경에선 H2 인메모리 DB를 사용하고 있었다. 이를통해 DB와 관련된 로직에서, 특히 하이버네이트와 관련하여 문제가 발생하고 있음을 유추했다.

디버깅을 통해 확인해본 결과 다음 메서드 안에서 예외가 발생하고 있었다.

1
aRepository.existsByUserIdAndFollowerId(...);

일단 디버깅을 통해 던져지는 예외의 종류를 살펴보았다.

위와 같이 AssertionError가 발생하고 있었다. AssertionError는 별도의 추가적인 예외 메시지가 없어 위 화면만 보고는 해결하기 난감한 상황이었다.

일단 예외가 발생하는 코드를 찾기위해 모든 Exception에 브레이크포인트를 지정하고 디버깅을 재개했다. Repository는 JPA가 구현해주고 중간중간 프록시, 요청 위임이 많기 때문에 코드의 흐름을 일일이 따라가기엔 어려웠기 때문이다.

가장 먼저 발생하는 예외는 NoSuchMethodException으로 existsByUserIdAndFollowerId를 찾지 못한다는 내용이다.

참고로 JPA는 내부적으로 프록시와 Reflection API를 사용하기 때문에 Class.java에서 에외가 잡힌 것이다.

AssertionError는 위 코드에서 발생하고 있었다. 앞서 existsByUserIdAndFollowerId를 찾지 못했기 때문에 assert문이 실패하는 것이다.

SQMSemantic Query Model의 약자로 하이버네이트 JPA 구현체에서 사용하는 일종의 쿼리 파서다. JPQL, Criteria와 같은 쿼리를 SQM으로 번역한뒤 SQM을 SQL로 파싱하여 실제 쿼리를 처리한다.

해결 방법

디버깅을 통해 얻은 키워드들을 조합하여 이리저리 검색해보았다. 그 중 유의미한 결과를 낸것은 jpa sqm AssertionError 검색어 였다. 최상단에 다음과 같이 공식 repo에 Issue가 등록되어 있었다.

Issue의 문제상황은

  • @IdClass를 사용한다.
  • 테스트 중에만 문제가 발생한다.
  • 예외 메시지가 거의 동일하다.
  • 발생한 의존성 버전과 발생한 날짜가 유사하다.

로 나와 매우 비슷한 문제 상황이었다. 커멘트에는 비슷한 문제를 겪은 사람이 여러명이 있었고 JPA 메인테이너는 하이버네이트에서 발생한 문제이며, 관련 이슈는 https://hibernate.atlassian.net/browse/HHH-16988 로 가보라는 댓글을 달았다.

하이버네이트 이슈에서 설명하는 상황은 나와는 살짝 달랐으나 기본 골격은 동일하다고 생각했다. 해당 버그는 6.2.5버전에선 발생하지 않고 있으며 6.2.6부터 문제가 발생한다. 내 프로젝트의 의존성도 6.2.6버전을 사용하고 있었다.

다행히 해당 버그는 우선순위가 주요로 측정되어 발견한지 2주도 안되어 해결되어 있었다. 문제가 발생하는 hibernate-core의 의존성 버전을 문제가 해결된 6.2.8로 올렸다.

그리고 테스트에 초록불이 들어왔다.

마치며

의존성 버전 관리의 중요성을 다시 한번 느꼈다. (왜 회사에선 최신 버전을 안쓰고 레거시 버전을 그대로 쓰는지도 한번더 이해가 됐다..)

사실 간단한 버그였지만 디버깅 과정이 나름 재밌던 버그라 글로 작성했다. 모든 예외에 브레이크 포인트를 거는 기능도 알고는 있었지만 이번에 처음 써봤다. 옛날엔 버그가 발생하면 무작정 구글에 검색을 하였는데 요즘은 디버깅을 통해 직접 버그를 찾아 해결하거나(에러 메시지가 생각보다 매우 친절한 것도 느낀다) 키워드를 함축한 후 검색을 하고 있다.

옛날엔 개발자의 기본 소양에 구글 검색이라고 답했는데 다시 묻게 된다면 공식문서, 책, 컨퍼런스를 읽고 듣기 위한 영어와 디버깅 능력을 답할 것 같다.

This post is licensed under CC BY 4.0 by the author.