[ 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편 #9 ] JPQL 기본 문법

김수호·2024년 5월 10일
0
post-thumbnail

JPQL 문법
( ex) select m from Member as m where m.username like '%kim% )

  • 엔티티와 속성은 대소문자를 구분한다. (Member, username)
  • JPQL 키워드는 대소문자를 구분하지 않는다. (SELECT, FROM, where)
  • 테이블 이름이 아닌 엔티티 이름 사용 (Member)
    • 엔티티 이름 = @Entity 의 name 속성을 의미한다. (default: 클래스명)
  • 별칭은 필수(m)
    • as 는 생략가능
  • 집합과 정렬
    • COUNT(m): 회원수
    • SUM(m.age): 나이 합
    • AVG(m.age): 평균 나이
    • MAX(m.age): 최대 나이
    • MIN(m.age): 최소 나이
    • GROUP BY, HAVING
    • ORDER BY

TypedQuery, Query

  • TypedQuery: 반환 타입이 명확할 때 사용
    • TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
  • Query: 반환 타입이 명확하지 않을 때 사용
    • Query query = em.createQuery("select m.username, m.age from Member m");

결과조회 API

  • query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트 반환
  • query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
    • 결과가 없으면: jakarta.persistence.NoResultException
      • 참고) Spring Data JPA 는 단일건 조회 함수 사용시 예외를 발생시키지 않음. (Optional or null 반환)
    • 둘 이상이면: jakarta.persistence.NonUniqueResultException

파라미터 바인딩

  • 이름 기준
    • em.createQuery("select m from Member m where m.username = :username", Member.class).setParameter("username", "member1")
  • 위치 기준
    • em.createQuery("select m from Member m where m.username = ?1", Member.class).setParameter(1, "member1")
    • 참고) 실무에서는 사용 지양
      • 순서 기반이므로 버그가 발생할 여지가 있다.

프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것
  • 프로젝션 대상
    • 엔티티
      • SELECT m FROM Member m -> 엔티티 프로젝션
      • SELECT t FROM Member m join m.team t -> 엔티티 프로젝션
      • 참고) 엔티티 타입의 결과는 영속성 컨텍스트의 관리 대상이 된다.
    • 임베디드 타입
      • SELECT o.address FROM Order o -> 임베디드 타입 프로젝션
      • 참고) 임베디드 타입은 엔티티 타입이 아닌 값 타입이므로 이렇게 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
    • 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
      • 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
      • SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션
      • 참고) 스칼라 타입은 영속성 컨텍스트에서 결과를 관리하지 않는다.
  • 프로젝션 - 여러 값 조회
    • ex) SELECT m.username, m.age FROM Member m
    • 1) Query 타입으로 조회
    • 2) Object[] 타입으로 조회
    • 3) new 명령어로 조회
      • 단순 값을 DTO로 바로 조회
        • SELECT new jpabook.jpashop.jpql.UserDto(m.username, m.age) FROM Member m
      • 패키지 명을 포함한 전체 클래스 명 입력
      • 순서와 타입이 일치하는 생성자 필요

페이징

  • JPA는 페이징을 다음 두 API로 추상화
    • setFirstResult(int startPosition): 조회 시작 위치 (0부터 시작)
    • setMaxResults(int maxResult): 조회할 데이터 수

조인

  • 내부 조인
    • 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

서브 쿼리

  • 나이가 평균보다 많은 회원
    • select m from Member m where m.age > ( select avg(m2.age) from Member m2 )
  • 한 건이라도 주문한 고객
    • select m from Member m where (select count(o) from Order o where m = o.member ) > 0
  • 참고)
    • 서브 쿼리 지원 함수
      • [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
        • {ALL | ANY | SOME} (subquery)
        • ALL: 모두 만족하면 참
        • ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참
      • [NOT] IN (subquery): 서브쿼리에 결과 중 하나라도 같은 것이 있으면 참
    • JPA 서브 쿼리 한계
      • JPA 는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
      • SELECT 절도 가능 (하이버네이트에서 지원)
      • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
        • 조인으로 풀 수 있으면 풀어서 해결
        • 참고) 하이버네이트6부터는 FROM 절의 서브쿼리 지원

JPQL 타입 표현

  • 문자: 'HELLO', 'She"s'
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • Boolean: TRUE, FALSE
  • ENUM: jpashop.jpabook.MemberType.Admin (패키지명 포함)
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)
    • select i from Item i where type(i) = Book
  • 참고) select m.username, 'HELLO', true from Member m where m.type = jpashop.jpabook.MemberType.Admin

JPQL 기타식

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

JPQL 조건식(CASE 등등)

  • CASE 식
    • 기본 CASE 식
      • select case when m.age >= 20 then '일반요금' else '학생요금' end from Member m
    • 단순 CASE 식
      • select case t.name when '팀A' then '인센티브100%' else '인센티브105%' end from Team t
  • COALESCE: 하나씩 조회해서 null이 아니면 반환
    • select coalesce(m.username, '이름 없는 회원') from Member m
      • 사용자 이름이 없으면 '이름 없는 회원'을 반환
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
    • select NULLIF(m.username, '관리자') from Member m
      • 사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환

JPQL 함수

  • JPQL 기본 함수
    • CONCAT: 문자 합치기
    • SUBSTRING: 문자 추출
    • TRIM: 공백 제거
    • LOWER: 소문자 변환
    • UPPER: 대문자 변환
    • LENGTH: 문자 길이 반환
    • LOCATE: 특정 문자열에서 지정한 문자열이 처음 나타나는 지점의 위치를 반환
    • ABS, SQRT, MOD
    • SIZE, INDEX(JPA 용도)
  • 사용자 정의 함수 호출
    • 하이버네이트는 사용전 방언에 추가해야 한다.
      • 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.
      • select function('group_concat', i.name) from Item i

JPQL - 경로 표편식

  • .(점)을 찍어 객체 그래프를 탐색하는 것
  • ex) select m.username from Member m join m.team t join m.orders o where t.name = 'TEAMA'
    • 위와 같 .(점) 경로 표현식을 사용하면 필드를 탐색하게 된다.
    • 필드의 종류는 크게는 상태 필드와 연관 필드가 있고, 연관 필드는 다시 단일 값 연관 필드와 컬렉션 값 연관 필드로 나뉜다.
      • 이 세가지는 내부적으로 동작방식이 다르기 때문에 잘 구분해서 이해해야 한다.
  • 경로 표현식 용어 정리
    • 상태 필드(state field): 단순히 값을 저장하기 위한 필드 (ex. m.username)
    • 연관 필드(association field): 연관관계를 위한 필드
      • 단일 값 연관 필드
        • @ManyToOne, @OneToOne 처럼 대상이 엔티티인 경우 (ex. m.team)
      • 컬렉션 값 연관 필드
        • @OneToMany, @ManyToMany 처럼 대상이 컬렉션인 경우 (ex. m.orders)

경로 표현식 특징

  • 상태 필드(state field): 경로 탐색의 끝, 더이상 탐색 X
    • select m.username, m.age from Member m
  • 단일 값 연관 경로: 묵시적 내부 조인 (inner join) 발생, 탐색 O
    • JPQL: select o.member from Order o
    • SQL: select m.* from Orders o inner join Member m on o.member_id = m.id
  • 컬렉션 값 연관 경로: 묵시적 내부 조인 방식, 탐색 X
    • select t.members from Team t
    • select t.members.username from Team t => X
    • 참고) FROM 절에서 명시적 조인을 통해 별칭을 얻으면, 별칭을 통해 탐색 가능
      • select m.username from Team t join t.members m
  • 참고) 실무에서는 묵시적 조인으로 사용되는 것 자체가 좋지 않다. 명시적 조인(join 키워드 직접 사용)을 사용해야 한다. 그래야 쿼리 튜닝등에 용이하고 리스크 발생 위험이 적다.
    • JPQL: select o.member.team from Order o
      • 위 JPQL에 대해 실행되는 실제 SQL 을 보면, 조인이 2번 일어난다.

명시적 조인, 묵시적 조인

  • 명시적 조인: join 키워드 직접 사용
    • select m from Member m join m.team t
  • 묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생 (내부 조인만 가능, 외부 조인은 불가능) (외부 조인을 하고자 하는 경우 명시적 조인을 해야 한다.)
    • select m.team from Member m

경로 탐색을 사용한 묵시적 조인 시 주의사항

  • 항상 내부 조인이 발생한다.
  • 컬렉션은 경로 탐색의 끝이므로, 명시적 조인을 통해 별칭을 얻어야 계속적인 경로 탐색이 가능하다.
  • 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM (JOIN) 절에 영향을 준다.
  • 실무 조언) 가급적 묵시적 조인 대신에 명시적 조인을 사용하자.
    • 조인은 SQL 튜닝에 중요 포인트이다.
    • 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다.

강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글