노트

JPQL과 @Query

JPQL (Jakarta Persistence Query Language) and @Query

백엔드#jpa · 연결된 개념 9개

쉽게 말하면

통역 앱에 한국어로 말하면 상대 언어로 옮겨 주듯, JPQL은 테이블 대신 엔티티와 필드 이름으로 쓴 쿼리를 연결된 DB의 SQL로 옮겨 실행해 줘요. 객체로 생각하면서 조인 같은 조회를 직접 적을 수 있죠.

비유가 깨지는 곳 통역이 모든 표현을 옮기진 못하듯 JPQL은 SQL 표준 일부만 지원해서 윈도 함수 같은 건 네이티브 쿼리를 써요. 또 벌크 UPDATE는 영속성 컨텍스트를 건너뛰니 clearAutomatically로 비워요.

JPQL(Jakarta Persistence Query Language)은 테이블이 아니라 엔티티와 필드를 대상으로 쓰는 JPA의 객체 지향 쿼리 언어다. 실행할 때 연결된 DB의 SQL로 번역된다. Spring Data에서는 @Query로 리포지터리 메서드에 직접 붙인다.

public interface ContactRepository extends JpaRepository<Contact, Long> {
 
    @Query("select c from Contact c where c.status = :status and c.createdAt > :after")
    List<Contact> findRecent(@Param("status") String status, @Param("after") LocalDateTime after);
 
    @Query("select o from Order o join fetch o.customer where o.id in :ids")
    List<Order> findWithCustomer(@Param("ids") List<Long> ids);       // N+1 방지
 
    @Query(value = "select * from contact where tsv @@ plainto_tsquery(:q)", nativeQuery = true)
    List<Contact> search(@Param("q") String q);                      // DB 전용 기능
 
    @Modifying(clearAutomatically = true)
    @Transactional
    @Query("update Contact c set c.status = :status where c.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") String status);
}

언제 무엇을

  • 조건이 단순하면 메서드 이름 쿼리(Spring Data 리포지터리와 쿼리 메서드)
  • 조건이 많거나 조인·fetch join이 필요하면 @Query + JPQL
  • 윈도 함수(window function), 전문 검색(full-text search)처럼 DB 고유 기능이 필요하면 nativeQuery = true. 대신 DB를 바꾸면 고쳐야 한다
  • 조건이 런타임에 조합되는 검색 화면이라면 Criteria API나 Querydsl 같은 타입 안전한 빌더

변경 쿼리

  • UPDATE·DELETE에는 @Modifying이 필요하고, 트랜잭션 안에서 실행해야 한다(@Transactional)
  • 벌크 쿼리(bulk query)는 영속성 컨텍스트를 거치지 않고 DB에 바로 간다. 이미 로드된 엔티티는 옛 값을 들고 있으므로 clearAutomatically = true로 비우거나 다시 조회한다. 변경 감지도 동작하지 않는다

Named Query

@NamedQuery(JPQL), @NamedNativeQuery(SQL)는 쿼리를 엔티티 쪽에 이름으로 미리 정의한다. 앱이 시작할 때 문법을 검사한다는 장점이 있지만, 쿼리가 엔티티에 흩어져 요즘은 @Query를 더 많이 쓴다. 여러 개는 @NamedQueries로 묶는다.

한계

JPQL은 SQL 표준의 일부만 지원한다. 쿼리가 복잡해지면 억지로 맞추기보다 네이티브 쿼리나 JDBC와 JdbcTemplate이 낫다. 조인으로 연관을 함께 가져오는 이유는 N+1 문제을 본다.

책: 8장. 운영에서 터지는 것들 — N+1

출처: Spring Data JPA 문서: Using @Query · Modifying Queries · Named Queries · Hibernate User Guide: HQL/JPQL

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Oracle PL/SQL

    PL/SQL(Procedural Language/SQL)은 Oracle이 SQL에 변수·조건문·반복문·예외 처리를 더한 절차형 언어(procedural language)다. DB 안에서 함수·프로시저(stored procedure)·트리거(trigger)를 만들어 로직을 데이터 가까이에서 돌린다. 다른 DB에도 비슷한 것(PostgreSQL의 PL/pgSQL 등)이 있지만 문법은 제각각이다.

  • GraphQL

    GraphQL은 클라이언트가 필요한 데이터의 모양을 쿼리로 적어 보내면 서버가 정확히 그 모양으로 응답하는 API 쿼리 언어(query language)다. Facebook이 2012년 내부에서 만들어 2015년 공개했고, 지금은 GraphQL Foundation이 관리한다.

  • Lombok

    Lombok은 게터·세터·생성자·toString 같은 반복 코드를 어노테이션으로 대신 생성해 주는 자바 라이브러리다. 컴파일할 때 어노테이션 처리기(annotation processor)가 바이트코드(bytecode)에 메서드를 넣는다. 스프링과는 별개 라이브러리라 따로 추가한다.

  • 데이터 무결성

    데이터 무결성(data integrity)은 저장된 데이터가 정확하고 일관되며 믿을 수 있는 상태로 유지되는 것이다. 재고가 100개로 보이는데 실제로 50개라면 무결성이 깨진 것이다. 관계형 DB는 이를 제약 조건(constraint)으로 강제한다.

  • 페이지네이션 (offset·cursor)

    페이지네이션(pagination)은 큰 목록을 한 번에 다 주지 않고 조각으로 나눠 주는 방법이다. 크게 오프셋 방식(offset-based, 몇 번째부터 몇 개)과 커서 방식(cursor-based, 이 항목 다음부터 몇 개)으로 나뉜다.

보기 옵션