JPA

[JPA] FETCH JOIN ORDER문 사용

코딩 못하는 감자 2025. 3. 17. 22:27

상황

사용자 테이블과 사용자의 스케줄 테이블을 Fetch Join을 통해 한번에 가져오려는 과정에서 쿼리 문법 오류가 발생하였다.

사용자 스케줄을 가져올 때 오름차순으로 가져오기 위해 order by 절을 사용하지 못하는 오류였다.

이유

아래는 문제의 코드이다.

사용자를 나타내는 UserEntity와 사용자의 스케줄을 나타내는 UserSchedule의 컬렉션 userSchedules 는 1:N 관계를 맺고 있다.

JPA의 FETCH JOIN은 컬렉션 정렬을 보장하지 않기 때문에, 오류가 발생하였다.

@Query("SELECT u FROM UserEntity u " +
       "LEFT JOIN FETCH u.userSchedules us " +
       "WHERE u.id = :userId " +
       "ORDER BY us.takeTime ASC") // ❌ 오류 발생 가능

반대로 아래의 경우는 ORDER BY 사용이 가능하다.

@Query("SELECT r FROM RoutineEntity r " +
       "JOIN FETCH r.userSchedule us " +  // (N:1) 관계이므로 문제 없음
       "LEFT JOIN FETCH r.routineMedicines rm " + // (1:N) 관계이지만 뒤에 위치
       "WHERE r.user.id = :userId " +
       "AND r.takeDate BETWEEN :startDate AND :endDate " +
       "ORDER BY r.takeDate ASC, us.takeTime ASC, rm.id ASC")

이러한 결과가 출력된 이유가 뭘까?

SQL 실행 방식과 JPA의 엔티티 매핑 방식이 다르기 때문이다.

원래는 하나의 부모엔티티가 N개의 자식 엔티티를 가져야하지만,

FETCH JOIN을 하게 되면 아래 표와 같이 데이터가 중복으로 발생하게 된다.

🔥 문제 발생 (데이터 중복)

user_id user_name schedule_id take_time

1 홍길동 101 08:00:00
1 홍길동 102 12:00:00
1 홍길동 103 18:00:00

이때 중복된 값들을 제거해주는 과정을 거치는데, 이 과정에서 정렬된 값들이 섞이게 되어, 정렬 정보가 무시되는 현상이 발생하게 된다.

또한 @OneToMany 관계에서 컬렉션을 저장하기 위해 자바에서는 보통 LinkedHashSet이나 List를 사용하는데, SQL 응답 형식은 순차적인 배열형태가 아니다.

즉 응답을 List에 매핑을 하게 되는데, 이 과정에서 JPA는 정렬된 응답을 순차적으로 List에 담지않는다.

해결 방법

이러한 상황을 해결하기 위해 JPA는 @OrderBy 어노테이션을 제공한다.

@OrderBy를 추가하게 되면, JPA는 엔티티 로딩 후 메모리에서 컬렉션을 정렬한다.

선 매핑 → 정렬

@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@OrderBy("takeTime ASC") 
private List<UserScheduleEntity> userSchedules;

결론

JPA는 SQL을 사용하지 않고 간편하게 데이터베이스 쿼리를 할 수 있다는 점에서 매우 간편하고 강력하지만,

데이터베이스와의 작동 방식 차이로 인한 문제점들이 존재한다는 것을 한번 더 느끼게 되었다.

하지만 문제를 해결하기 위해 제공하는 기능들과 원리에 대해서 이해한다면, 생산성 면에서 엄청난 이점을 가져올 수 있다는 것을 알게되었다.