JPQL(Java Persistence Query Language)란?
Java에서 사용하는 객체 지향 쿼리 언어이다.
따라서 Table 대상 쿼리가 아닌 Entity 객체 대상 쿼리문이다.

JPQL은 SQL을 추상화해서, 특정 DB에 의존하지 않는다.
마지막에 SQL로 변환이 되어 쿼리가 나간다.

기본 JPQL 문법

[사진 첨부]

  • 기본적으로 em.createQeury()를 사용한다.
    • em.createQuery() 를 날리면, 영속 Context가 관리하게 된다.
  • Entity와 Attribute는 대소문자를 구분한다.
  • JPQL Query 키워드는 대소문자를 구분하지 않는다.
  • Entity 이름을 사용하며, Table 이름을 쓰는 것이 아니다!
  • 별칭(Alias)은 필수! (AS 키워드 생략 가능)
  • 집합과 정렬이 모두 사용 가능하다.
    • COUNT, SUM, AVG, MAX, MIN
    • GROUP BY, HAVING
  • Type
    • TypeQeury : 반환 타입이 명확할 때 사용한다.
    • Query : 반환 타입이 명확하지 않을 때 사용한다.
  • 결과 조회 API
    • getResultList() : Collection으로 반환한다. Loop문으로 접근 가능.
      • 결과가 없으면, 빈 Collection이 반환된다.
    • getSingleResult() : 결과가 정확히 하나만 나온다.
      • 결과가 없다면? NoResultException이 터진다.
      • 결과가 둘 이상이면? NonUniqueResultException이 터진다.

프로젝션

SELECT Clause에 조회할 대상을 지정하는 행위이다.

  • 프로젝션 대상 : Entity, Embedded, Scalar(기본 값 타입)
  • SQL과 마찬가지로 DISTINCT로 중복을 제거할 수 있다.

프로젝션 : 여러 종류의 값 조회

  • SELECT m.username, m.age FROM Member m 이런 Query가 있다.
    • getResultList()를 하게 된다면, 어떤 Type에 값을 담아야 할까?
  • new 명령어로 조회를 하자!
    • 단순 값을 DTO로 바로 조회한다.
    • Query의 결과를 선언된 DTO의 형태로 받아 저장한다.
    • DTO에는 Parameter와 Type이 일치하는 생성자가 필요하다.

[사진 첨부]

페이징

페이징이란, SQL에서의 LIMIT, OFFSET과 같은 역할이다.
조회하는 객체의 수를 제한할 수 있는 Query이다.

.setFirstResult(integer).setMaxResults(integer)로 제어한다.

조인

SQL에서 사용하는 JOIN 기능을 JPQL에선 이렇게 다룬다.

  • 내부 조인
    • ```SELECT m FROM Member m (INNER) JOIN m.team t
  • 외부 조인
    • SELECT m FROM Member m LEFT (OUTER) JOIN m.team t
  • 세타 조인(크로스 조인)
    • SELECT count(m) from Member m, Team t where m.username = t.name
    • 팀 이름과 유저 이름이 동일한 Member의 수를 반환한다.
  • JOINON
    • 조인 대상을 필터링하는 기능을 한다.
      • SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'A'
    • 연관 관계가 없는 엔티티를 외부 조인하도록 한다(< Hibernate 5.1).
      • SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name

서브 쿼리

SQL에서 사용하는 Subquery와 동일하게 JPQL에서 사용한다.

  • 나이가 평균보다 많은 회원
SELECT m from Member m
where m.age > (SELECT avg(m2.age) from Member m2)

보통 이렇게 본 쿼리의 결과를 Subquery와 연관 없이 만들어야 성능이 나온다.

  • 한 건이라도 주문한 고객
SELECT m from Member m
where (SELECT count(o) from Order o where m = o.member) > 0

이렇게 본 쿼리의 결과를 Subquery에 끌어다 쓰면, 보통 성능이 떨어진다.

  • EXISTS : 서브쿼리에 결과가 존재하면 True가 된다.
  • ALL / ANY / SOME
    • ALL : 모든 Data가 조건을 만족하면 참이 된다.
    • ANY, SOME : 조건을 하나라도 만족하면 참이 된다.
  • IN : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다.

JPA는 서브쿼리에 명확한 한계가 존재한다.
표준 스펙에서는 WHERE 절과 HAVING 절에서만 서브쿼리의 사용이 가능하다.
하지만, Hibernate에서는 SELECT 절의 서브쿼리 사용도 허가하고 있다. FROM 절의 서브 쿼리는 현재 JPQL에서는 불가능 하며, 조인으로 풀 수 있으면 풀자!

JPQL 타입 표현

  • 문자 : ‘Hello’, ‘She’’s’ 와 같이 따옴표로 표현한다.
  • 숫자 : 10L(Long), 10D(Double), 10F(Float)
  • Boolean : TRUE, FALSE
  • ENUM : jpabook.MemberType.ADMIN 와 같이 패키지를 모두 써야한다.
    • 하지만, JPQL에서는 setParameter()를 통해 패키지 명 없이 집어넣을 수 있다.
  • Entity : TYPE(m) = Member 상속 관계에서 사용된다.
    • SELECT i from Item i WHERE TYPE(i) == Book 과 같이 엔티티 타입을 건다.
    • 해당 TYPE은, 상속 관계에서 @DiscriminatorValue에서 뽑힌 DTYPE이다.

조건식(CASE)

  • 기본 CASE식
    • [사진 첨부]
    • 마치 switch - case 문과 같이 사용하도록 한다.
  • 단순 CASE식
    • [사진 첨부]
    • 위의 기본 CASE식을 단순형으로 줄일 수 있다.
  • COALESCE : 하나씩 조회해서 NULL이 아니면 반환.
    • SELECT COALESCE(m.username, '이름 없음') FROM Member m
      • 사용자 이름이 NULL이면, ‘이름 없음’ 반환.
  • NULLIF : 두 값이 같으면 NULL을 반환하고, 다르면 첫번째 값 반환.
    • SELECT NULLIF(m.username, '관리자') FROM Member m
      • 사용자 이름이 관리자면, NULL을 반환. 아니면 본인의 이름 반환.

JPQL 기본 함수

  • CONCAT : 말 그대로, 문자열 2개를 합치는 함수이다.
    • Hibernate는 문자열 2개를 더할 때, ||도 제공을 한다.
  • SUBSTRING : 문자열을 특정 길이만큼 잘라내는 함수이다.
  • TRIM : 공백 제거
  • LOWER, UPPER : 대소문자 변경
  • LENGTH : 문자열의 길이 반환
  • LOCATE : 해당 문자열이 위치하는 시작 Index를 반환한다.
    • SELECT LOCATE('de', 'abcdefg') FROM Mebmer m
    • 4가 반환이 된다.
  • ABS, SQRT, MOD : 절대값, 루트, 나머지 등 수학 함수
  • SIZE, INDEX
    • SIZE : Collection의 Size를 반환한다.
    • INDEX : @OrderColumn 사용 시, Collection의 위치 값을 구한다.
      • 웬만하면 쓰지 않는다!

사용자 정의 함수

Hibernate에서는, 사용 전 방언 Class에 추가를 해서 사용자 정의 함수를 등록할 수 있다.
아래와 같이 등록을 할 수 있으며, 형태는 이미 등록된 함수를 참조하자.

public class MyH2Dialect extends H2Dialect{
    public MyH2Dialect(){
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.String));
    }
}

사용은 아래와 같은 SQL문으로 사용이 가능하다.
SELECT function('group_concat', m.username) FROM Member m";