JPQL 문법
( ex) select m from Member as m where m.username like '%kim%
)
- 엔티티와 속성은 대소문자를 구분한다. (Member, username)
- JPQL 키워드는 대소문자를 구분하지 않는다. (SELECT, FROM, where)
- 테이블 이름이 아닌 엔티티 이름 사용 (Member)
- 엔티티 이름 = @Entity 의 name 속성을 의미한다. (default: 클래스명)
- 별칭은 필수(m)
- 집합과 정렬
- 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 튜닝에 중요 포인트이다.
- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다.
강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.