Post

Jpql

Jpql

JPQL (Java Persistence Query Language)

JPQL이 필요한 이유

JPA만 사용할 경우 데이터 조회 방법은 기본적으로 아래 두 가지뿐이다.

  • 식별자를 통한 조회
    • em.find(Member.class, id)
  • 객체 그래프 탐색
    • member.getTeam().getName()


이 방식만으로 애플리케이션을 개발하면 문제가 생긴다.

모든 데이터를 메모리에 올려둔 뒤 application 단에서 조건을 걸어 필터링해야 한다


→ 필요 없는 데이터까지 전부 조회하게 되어 성능 낭비가 발생한다.



이러한 문제를 해결하기 위해

JPA는 DB에서 조건에 맞는 데이터만 조회할 수 있도록 JPQL(Java Persistence Query Language) 을 제공한다.





JPQL이란?

  • JPQL은 JPA에서 제공하는 객체 지향 쿼리 언어
  • SQL을 추상화하여 만들었기 때문에 DB에 의존적이지 않다.
  • 문법은 SQL과 매우 유사하다.



SQL vs JPQL 차이

구분SQLJPQL
대상테이블엔티티
조회 기준컬럼필드
의존성DB 의존DB 독립



JPQL 예시

1
select m from Member m where m.name like '%hello%'
  • Member는 테이블 이름이 아니다
  • @Entity(name = "")에 지정된 엔티티 이름
  • 지정하지 않았다면 클래스 이름이 기본값





Query Method

Spring Data JPA에서는 JpaRepository만 상속하면 메서드 이름만으로 쿼리를 자동 생성해준다.

1
2
3
public interface CommentRepository extends JpaRepository<Comment, Long> {
    List<Comment> findAllByRegistryId(Long idx);
}



장점

  • 구현 코드가 필요 없다
  • 간단한 조건 조회에 매우 편리하다



단점

  • 조건이 복잡해질수록 메서드 이름이 지나치게 길어진다
  • 가독성이 급격히 떨어진다





Query Method의 한계

정렬 예시

1
2
3
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByNameContainsOrderByPriceAsc(String name);
}

이 메서드는 다음 의미를 가진다.

  • name을 포함하고
  • price 기준 오름차순 정렬


하지만 조건이 늘어나면 메서드 이름이 지나치게 길어지고 읽기 어려워진다.


그래서 정렬을 파라미터로 분리할 수 있다.

1
List<Product> findByNameContains(String name, Sort sort);



테스트 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@DataJpaTest
class ProductRepositoryTest {

    @Test
    void findByNameContains() {
        List<Product> products =
            productRepository.findByNameContains(
                "네스프레소",
                Sort.by(
                    Sort.Order.asc("name"),
                    Sort.Order.asc("price")
                )
            );
    }
}



페이징 예시

1
2
3
public interface ProductRepository extends JpaRepository<Product, Long> {
    Page<Product> findAll(Pageable pageable);
}
1
2
3
4
5
6
7
8
9
@DataJpaTest
class ProductRepositoryTest {

    @Test
    void findAllPaging() {
        Page<Product> products =
            productRepository.findAll(PageRequest.of(0, 5));
    }
}
  • PageRequestPageable의 구현체
  • 페이징과 정렬을 함께 사용할 수도 있다



문제

  • 메서드 이름이 여전히 길다
  • 복잡한 조건은 표현하기 어렵다
  • 가독성이 좋지 않다


→ 이럴 때 JPQL을 사용한다.





JPQL 기본 문법

  • 엔티티 이름과 필드는 대소문자를 구분
  • SELECT, FROM, WHERE 같은 키워드는 대소문자 구분 없음
  • 별칭(alias)은 필수
  • as 키워드는 생략 가능
1
SELECT m FROM Member m


JPQL은 @Query 어노테이션을 통해 사용한다.



JPQL 작성하기 (@Query)

1
2
@Query("SELECT p FROM Product p WHERE p.category = ?1")
List<Product> findByCategory(String category);
1
2
@Query("SELECT p FROM Product p WHERE p.category LIKE %:category%")
List<Product> findByCategory2(@Param("category") String category);
1
2
@Query("SELECT p.name, p.category, p.price FROM Product p WHERE p.category LIKE %:category%")
List<Object[]> findByCategory3(@Param("category") String category);


파라미터 전달 방식

1. 위치 기반 파라미터

  • ?1, ?2
  • 순서가 바뀌면 오류 발생 가능

2. 이름 기반 파라미터 (권장)

  • :category
  • @Param과 함께 사용


LIKE

표현의미
:category정확히 일치
%:category%앞뒤 모두 포함
:category%해당 값으로 시작


ex) "연어"

  • %연어% → 새우연어, 연어초밥
  • 연어% → 연어초밥만
  • 연어 → 정확히 연어만



일부 컬럼만 조회할 경우

1
2
3
4
5
6
7
8
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String category;
    private int price;
}


3개 컬럼만 조회하면 엔티티로 받을 수 없고 Object 배열로 받아야 한다.

1
List<Object[]> findByCategory(@Param("category") String category);

List<Product> 사용 불가



JPQL의 단점

  • 쿼리를 문자열로 작성
  • 오타가 있어도 컴파일 시점에 알 수 없다
  • 실행 시점에서야 오류 확인 가능





QueryDSL이란?

문자열이 아닌 코드로 쿼리를 작성하는 프레임워크


JPQL의 단점을 보완한다.


QueryDSL 장점

  • 컴파일 시점에 문법 오류 확인 가능
  • IDE 자동 완성 지원
  • 동적 쿼리 작성이 쉬움
  • 조건 재사용이 가능


QueryDSL 단점

  • 초기 설정이 까다롭다






REFERENCE
Spring Data JPA - Reference Documentation
[JPA] JPQL Query 정리
쿼리 메소드, JPQL, Querydsl 요약

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