1. 객체지향 쿼리 소개
JPQL
- 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않음
JPQL 소개
- 엔티티 객체를 조회하는 객체지향 쿼리
- SQL을 추상화 해서 특정 데이터베이스에 의존하지 않음
- SQL보다 간결(엔티티 직접 조회, 묵시적 조인, 다형성 지원)
Criteria 쿼리 소개
- JPQL을 생성하는 빌더 클래스
- 문자가 아닌 프로그래밍 코드로 JPQL 작성 가능 ➡️ 컴파일 시점에 오류 발견 가능
- IDE를 사용하면 코드 자동완성 지원
- 동적 쿼리 작성하기 편함
QueryDSL 소개
- JPQL 빌더(JPA 표준은 아니고, 오픈소스 프로젝트)
- 코드 기반이며 단순하고 사용하기 쉬움
- 어노테이션 프로세서를 사용해서 쿼리 전용 클래스 만들어야 함
네이티브 SQL 소개
- SQL을 직접 사용하는 기능
- 특정 데이터베이스에 의존하는 SQL을 작성해야 한다는 단점
JDBC 직접 사용, 마이바티스 같은 SQL 매퍼 프레임워크 사용
- JDBC나 마이바티스를 JPA와 함께 사용하면 영속성 컨텍스트를 적절한 시점에 강제로 플러시해야 함
- 이를 해결하는 방법은 JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시해서 데이터베이스와 영속성 컨텍스트를 동기화하면 됨
2. JPQL
기본 문법과 쿼리 API
SELECT문
- 대소문자 구분
- 엔티티와 속성은 대소문자 구분
- SELECT, FROM, AS 같은 JPQL 키워드는 대소문자 구분X
- 엔티티 이름
- 별칭 필수
TypeQuery, Query
- TypeQuery: 반환할 타입을 명확하게 지정 가능
- Query: 반환할 타입 명확하게 지정 불가
- Query 객체는 SELECT 절의 조회 대상이 둘 이상이면 Object[]를 반환하고 SELECT 절의 조회 대상이 하나면 Object 반환
결과 조회
- query.getResultList(): 결과를 예제로 반환, 결과 없으면 빈 컬렉션 반환
- query.getSingleResult(): 결과가 정확히 하나일 때 사용
- 결과 없으면 javax.persistence.NoResultException 예외 발생
- 결과가 1개보다 많으면 javax.persistence.NonUniqueResultException 예외 발생
파라미더 바인딩
- 파라미터 바인딩은 선택 아닌 필수!
- 이름 기준 파라미터: 파라미터를 이름으로 구분, 이름 기분 파라미터 앞에 : 사용
- 위치 기준 파라미터: ? 다음에 위치 값 주면 됨
프로젝션
- 프로젝션: SELECT 절에 조회할 대상을 지정하는 것
- 대상: 엔티티, 임베디드 타입, 스칼라 타입
- 엔티티 프로젝션
- 임베디드 타입 프로젝션
- 임베디드 타입은 조회의 시작점이 될 수 없음
- 임베디드 타입은 값 타입. 따라서 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리 X
- 스칼라 타입 프로젝션
- 스칼라 타입: 숫자, 문자, 날짜와 같은 기본 데이터 타입
- 중복 데이터를 제거하려면 DISTINCT 사용
- 통계 쿼리도 주로 스칼라 타입으로 조회
- 여러 값 조회
- 프로젝션에 여러 값을 선택하면 Query 사용
- NEW 명령어
- SELECT 다음에 NEW 명령어를 사용하면 반환 받을 클래스 지정 가능
➡️ NEW 명령어를 사용한 클래스로 TypeQuery 사용할 수 있어서 지루한 객체 변환 작업을 줄일 수 있음
- 패키지 명을 포함한 전체 클래스 명을 입력해야 함
- 순서와 타입이 일치하는 생성자 필요
페이징 API
- setFirstResult (int startPosition): 조회 시작 위치(0부터 시작)
- setMaxResults (int maxResult): 조회할 데이터 수
집합과 정렬
집합 함수
- COUNT: 결과 수
- MAX, MIN: 최대, 최소 값
- AVG: 평균값
- SUM: 합
집합 함수 사용 시 참고 사항
- NULL 값은 무시하므로 통계에 잡히지 X
- 값이 없는데 SUM, AVG, MAX, MIN 사용하면 NULL, COUNT는 0
- DISTINCT를 집합 함수 안에 사용해서 중복된 값 제거하고 나서 집합 구할 수 있음
- DISTINCT를 COUNT에서 사용할 때 임베디드 타입 지원 X
GROUP BY, HAVING
- GROUP BY: 통계 데이터를 구할 때 특정 그룹끼리 묶어줌
- HAVING: GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링
정렬(ORDER BY)
JPQL 조인
내부 조인
- INNER JOIN = JOIN
- JPQL 조인의 가장 큰 특징은 연관 필드 사용
- 연관 필드: 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드
외부 조인
- LEFT JOIN = LEFT OUTER JOIN
컬렉션 조인
- 일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인
세타 조인
JOIN ON 절
- ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있음
- 보통 외부 조인에서만 사용
페치 조인
- JPQL에서 성능 최적화를 위해 제공
- 연관된 엔티티나 컬렉션을 한 번에 같이 조회
- join fetcch
- 별칭 사용 불가
- DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복 제거
- JPQL은 결과를 반환할 때 연관관계까지 고려하지 않음. 단지 SELECT 절에 지정한 엔티티만 조회
- 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 있어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있음
- 연관된 엔티티를 쿼리 시점에 조회하므로 지연 로딩이 발생하지 않아 준영속 상태에서도 객체 그래프를 탐색할 수 있음
- 둘 이상의 컬렉션을 페치할 수 없음: 컬렉션 * 컬렉션의 카테시안 곱이 만들어지므로 주의
- 컬렉션을 페치 조인하면 페이징 API 사용 불가
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적
- 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적
경로 표현식
- 상태 필드: 단순히 값을 저장하기 위한 필드
- 연관 필드: 연관관계를 위한 필드, 임베디드 타입 포함
- 단일 값 연관 필드: @ManyToOne, @OneToOne, 대상이 엔티티
- 컬렉션 값 연관 필드: @OneToMany, @ManyToMany, 대상이 컬렉션
경로 표현식과 특징
- 상태 필드 경로: 경로 탐색의 끝
- 단일 값 연관 경로: 묵시적으로 내부 조인이 일어남. 단일 값 연관 경로는 계속 탐색 가능
- 컬렉션 값 연관 경로: 묵시적으로 내부 조인 일어남. 더는 탐색할 수 없음. FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색 가능
- 묵시적 조인은 모두 내부 조인
서브 쿼리
- WHERE, HAVING 절에서만 사용가능
- SELECT, FROM 절에서는 사용 불가
서브 쿼리 함수
- EXISTS
- {ALL | ANY | SOME}
- IN
조건식
타입 표현
- 문자
- 숫자
- 날짜
- Boolean
- Enum
- 엔티티 타입
연산자 우선 순위
- 경로 탐색 연산(.)
- 수학 연산: +, -(단항 연산자), *, /, +, -
- 비교 연산: =, >, >=, <. <=. <>(다름), [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF], [NOT] EXISTS
- 논리 연산: NOT, AND, OR
컬렉션 식
- 컬렉션은 컬렉션 식 이외에 다른 식 사용 불가
CASE 식
CASE
{WHEN <조건식> THEN <스칼라식>}+
ELSE <스칼라식>
END
CASE <조건대상>
{WHEN <스칼라식1> THEN <스칼라식2>}+
ELSE <스칼라식>
END
COALESCE(<스칼라식> {, <스칼라식>}+)
NULLIF(<스칼라식>, <스칼라식>)
다형성 쿼리
- TYPE: 엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용
- TREAT: 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
사용자 정의 함수 호출
function_invocation::= FUNCTION(function_name {, function_arg}*)
기타 정리
- enum은 = 비교 연산만 지원
- 임베디드 타입은 비교 지원 X
- EMPTY STRING
- JPA 표준은 ''을 길이 0인 Empty String으로 정했지만 데이터베이스에 따라 ''를 NULL로 사용하는 데이터베이스도 있음
- NULL 정의
- 조건을 만족하는 데이터가 하나도 없으면 NULL
- NULL은 알 수 없는 값. NULL과의 모든 수학적 계산 결과는 NULL
- Null == Null은 알 수 없는 값
- Null is Null은 참
엔티티 직접 사용
- 기본 키 값
- JPQL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 기본 키 값을 사용
- 외래 키 값
Named 쿼리: 정적 쿼리
- 동적 쿼리: em.createQuery("select ..") 처럼 JPQL을 문자로 완성해서 직접 넘기는 것, 런타임에 특정 조건에 따라 JPQL을 동적으로 구성 가능
- 정적 쿼리: 미리 정의한 쿼리에 이름을 부여해서 필요할 대 사용, Named 쿼리는 한 번 정의하면 변경할 수 없는 정적 쿼리
- Named 쿼리는 애플리케이션 도이 시점에 JPQL 문법을 체크하고 미리 파싱
➡️ 오류 빨리 확인 가능, 파싱된 결과를 재사용 하므로 성능상 이점, 변하지 않는 정적 SQL이 생성되므로 데이터베이스의 조회 성능 최적화에 도움
- 어노테이션에 정의: @NamedQuery
- XML에 정의
- XML이 우선권 가짐