트러블 슈팅 (1)

김영준·2026년 4월 10일

멜로미

목록 보기
9/13

N+1 해결

단순히 Join을 쓰면 되는데 왜? 문제가 되었을까?

현 프로젝트는 GIN을 사용하여 관련도에 기반한 검색을 구현했음. 하지만 여기서 PostgreSQL의 similarity() 함수가 사용됨. 이 함수를 사용하기 위해선 Native Query를 날려야함. 원래는 JPQL의 @EntityGraph를 사용하는데, 여기선 해당 함수를 사용하기 위해 Native Query 를 사용해야함. 근데 Native Query를 사용하면 쿼리가 너무 길어지는 문제가 있음. 물론 길게길게 해도 되겠지만, 본인은 이걸 두 단계로 나눔.

  1. Native Query로 similarity() 계산 -> 정렬 -> ID만 반환함 (Join 없이 ID만)
  2. JPQL + EntityGraph 활용
    : ID 목록으로 author 자동 Join fetch 함.

개념 자세히

JPA

: JPA는 세 가지 종류가 있음
1. 메서드 이름으로 자동 생성

findByAuthorId(Long authorId)
// JPA가 자동으로 SQL 생성
  1. JPQL (객체 기준 쿼리 날림)
@Query("SELECT p FROM TherapyPost p WHERE p.deletedAt IS NULL")
// TherapyPost = 자바 클래스명
// deletedAt = 자바 필드명
// JPA가 이걸 SQL로 변환해줌
  1. Native Query (DB 기준 쿼리)
@Query(value = "SELECT p.id FROM therapy_posts p WHERE p.deleted_at IS NULL", 
       nativeQuery = true)
// therapy_posts = 실제 테이블명
// deleted_at = 실제 컬럼명
// JPA 변환 없이 그대로 DB로 날아감

근데 또 궁금한점

그냥 길게 Native Query쓰면 되잖아. 왜 2단계로 나눴을까?

문제 1. 페이지네이션

JPA의 Page<> 반환을 쓰려면 countQuery도 같이 써야함

@Query(value = "SELECT p.*, u.* ...", 
       countQuery = "SELECT count(*) ...",
       nativeQuery = true)
Page<TherapyPost> findByRelevance(...)

Native Query에서 Page<TherapyPost>로 받으면 JPA가 p.*u.*를 TherapyPost 객체에 맵핑하는걸 보장 못함
JPQL은 JPA가 서버 구조를 알고(어떤필드가 게시글을 나타내는지 등) 만든 쿼리라 맵핑이 보장됨
Native는 JPA가 모르는 쿼리를 날림. 따라서 컬럼 겹치거나 바로바로 맵핑이 안됨

<요약>

Native Query  →  DB 표현 (p.*, u.*)
Page<TherapyPost>  →  JPA 표현 (자바 객체)

둘이 언어가 달라서 JPA가 "이 컬럼이 어느 필드야?" 를 모름
→ 매핑 보장 안 됨

그래서 해결책
Native Query  →  Long(ID)만 반환   ← DB 표현 그대로, 단순한 타입
JPQL          →  Page<TherapyPost> ← JPA 표현, 매핑 보장


[1단계 native query]
  similarity 계산 + 정렬 + 페이지네이션
  → [5, 2, 8, 1] ID 목록 반환

[2단계 JPQL + EntityGraph]
  WHERE id IN (5, 2, 8, 1)
  → author JOIN해서 TherapyPost 객체로 매핑해서 반환

첫 번째로, countQuery를 NativeQuery로 추가적으로 날려줘야함
두 번째로, NativeQuery가 가리키는 데이터(u., p.)를 JPA가 맵핑을 못함

문제 2. 매핑 복잡도

native qyery 결과 + TherapyPost + User 컬럼이 섞인 ResultSet
JPA가 이걸 TherapyPost.author 까지 자동 매핑? 보장 안됨

기본 개념 설명

JPA

: Java Persistence API
자바 객체와 DB 테이블을 연결해주는 표준 인터페이스

자바 객체  ↔  JPA  ↔  DB 테이블
TherapyPost    ↔       therapy_posts

즉, 직접 SQL 안써도 자바 객체로 DB를 다룰 수 있음
JPA는 스펙이고 실제 구현체는 하이버네이트임.

JPQL

: Java Persistence Query Language
JPA에서 쓰는 객체 기준 쿼리 언어

-- SQL (테이블/컬럼 기준)
SELECT * FROM therapy_posts WHERE deleted_at IS NULL

-- JPQL (객체/필드 기준)
SELECT p FROM TherapyPost p WHERE p.deletedAt IS NULL

SQL이랑 생김새가 비슷한데 테이블명 대신 클래스명, 칼럼명 대신 필드명을 사용함. JPA가 이걸 받아서 SQL로 변환하여 DB로 날림

근데 "offSet"기반 페이지네이션이 아니다!! 위 설계 다 갈아엎고 "커서기반"으로 바꿔야함

profile
개발의 신이 될거다

0개의 댓글