2 분 소요

문제상황

Member와 Application 은 OneToOne 관계이고 지연 로딩 세팅을 했음에도 Member를 조회하면 Application이 즉시 로딩되는 문제가 발생했다.

이 문제의 원인과 해결한 방법을 공유하고자 글을 쓴다.


domain

Member (유저)

img1


Application (멘토 인증 신청서)

img1

설명에 필요한 매핑 정보를 제외한 부분은 거둬냈다.


연관관계

위의 매핑 정보를 보면 알 수 있듯이 연관관계의 주인은 Application이고 XToOne 관계의 기본 fetch 전략은 EAGER이기 때문에 LAZY 세팅을 해준 모습을 볼 수 있다.

즉시 로딩 발생

현재 프로젝트는 Jwt 토큰 기반으로 인증/인가 처리를 하고 있기 때문에 요청이 들어올 때마다 JwtAuthenticationFilter에서 access Token을 검증하고 유저의 이메일을 추출한다. 그리고 이메일을 통해 유저 정보를 찾고 SecurityContextHolder에 인증 객체를 저장한다.

그런데 유저를 찾는 과정에서 Application이 즉시 로딩 돼서 Select 쿼리가 한 번씩 더 나가는 문제가 발생했다.

img1


Member 조회 시 나가는 쿼리

img1img1

분명 지연 로딩 세팅을 했음에도 불구하고 즉시 로딩으로 작동해서 select 쿼리가 한 번 더 나가는 것을 확인할 수 있었다.

→ 연관관계의 주인이 아닌 Member를 조회할 때 Application이 즉시 로딩되는 모습이다.


원인 분석

  1. 지연 로딩 조회가 가능하기 위해서는 JPA 구현체에서 프록시를 만들어야 된다.
  2. 그리고 연관 관계 엔티티는 null 또는 프록시 객체가 할당되어야 한다.
    • null은 연관관계에 해당한 엔티티가 존재하지 않음을 의미하고 프록시 객체는 연관관계에 해당하는 엔티티가 존재한다는 의미이다.

1, 2를 바탕으로 Application과 Member 테이블을 생각해 보자.


Application 관점

Application의 경우 연관관계의 주인이기 때문에 외래키로 member_id를 가지고 있다. 즉 Member의 존재를 파악할 수 있는 필드가 존재하는 것이다.

만약 member_id가 null이라면 연관된 Member가 없는 것이고 null이 아니라면 프록시 객체를 설정하여 지연 로딩을 할 수 있는 것이다.


Member 관점

Member의 경우 연관관계의 주인이 아니기 때문에 Application과 연결할 수 있는 필드 자체가 존재하지 않는다. 때문에 연관된 Application이 존재하지는 지 알 수도 없어서 null, 프록시 객체 중 어떤 값을 세팅할지 결정하기가 어렵다.

→ OneToOne 양방향 매핑 관계에서 연관관계의 주인이 아닌 Member를 조회하게 되면 프록시 객체를 생성할 수 없기 때문에 즉시 로딩으로 동작된다.


문제 해결

Member를 조회할 때 Application을 fetch join 하기

모든 로그인이 필요한 API에서 인증을 위해 Member가 조회되고 있는 상황이고 해당 로직에서 Application은 사용되지 않는다. 사용되지도 않는 Application을 모든 요청에 fetch join해서 가져오는 것은 적절하지 않다고 생각돼서 이 방식은 채택하지 않았다.


연관관계 재설정 Member(1) - Application(N) (최종 채택)

우리 프로젝트에서는 최종적으로 OneToOne 관계를 OneToMany 관계로 바꿈으로써 문제를 해결했다.

OneToMany

OneToMany의 경우 지연 로딩이 가능하다. 컬렉션을 사용하면 null을 표현할 수 있기 때문이다. 일단 프록시 객체를 만들어 두고 만약 조회했을 때 size = 0이라면 연관된 객체가 없는 것으로 판단할 수 있다.

선택 이유

멘토 인증과 같은 경우 사용자가 적절한 명함 사진을 올리지 않는 경우에 관리자가 인증을 거부할 수 있다. 때문에 하나의 Member가 여러 번의 Application (멘토 인증 신청)을 할 수 있다.

→ Member와 Application의 관계는 OneToOne보다는 OneToMany가 더 적절하다.

변경 후 Member

img1


변경 후 Application

img1


변경 후 쿼리

img1

Member 조회 시 Application이 즉시 로딩되지 않는 모습을 볼 수 있다.


느낀점

사실 처음부터 Member와 Application은 1:1이 아닌 1:N으로 설계하는 것이 맞다. 하지만 잘못된 설계로 인해 지연 로딩이 안되는 문제를 경험할 수 있었고 JPA에 대해서 더 깊게 이해할 수 있었던 것 같다.