JPQL / Criteria / QueryDSL / 네이티브 SQL / JDBC 직접 사용 / SQL 매퍼에 대한 개념을 정리한다. 10강은 내용이 많아 1편과 2편으로 나눠서 정리한다.
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (10-1) 객체 지향 쿼리 언어
10.1 객체지향 쿼리 소개
- ORM을 사용하면 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 아닌 엔티티 객체를 대상으로 하는 방법이 필요하여 만들어진 것이 JPQL
- JPQL 특징
- 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- JPA는 JPQL 뿐만 아니라 다양한 검색 방법 제공
- JPA가 공식 지원하는 기능
- JPQL (Java Persistence Query Language)
- Criteria 쿼리 : JPQL을 편하게 작성하도록 도와주는 AP, 빌더 클래스 모음
- Native Query : JPA에서 JPQL 대신 직접 SQL을 사용할 수 있음
- JPA가 공식 지원하는 것은 아니지만 사용하는 기술
- QueryDSL : Criteria 쿼리처럼 JPQL을 편하게 작동하도록 도와주는 빌더 클래스 모음
- JDBC 직접 사용, MyBatis와 같은 SQL 매퍼 프레임워크 사용 : 필요하다면 JDBC를 직접 사용할 수 있음
10.1.1 JPQL 소개
- 엔티티 객체를 조회하는 객체지향 쿼리
- 문법은 SQL과 비슷하고 ANSI 표준 SQL이 제공하는 기능을 유사하게 제공
- JPQL은 SQL을 추상화해서 특정 데이터베이스에 의존하지 않음
- 데이터베이스 방언만 변경하면 JPQL을 수정하지 않아도 자연드럽게 디비 변경가능 (Dialect)
- 엔티티 직접 조회, 묵시적 조인, 다형성 지원으로 SQL보다 코드가 간결
10.1.2 Criteria 쿼리 소개
- JPQL을 생성하는 빌더 클래스
- 문자가 아닌 코드로 JPQL을 작성할 수 있는 장점을 가지고 있음
- JPQL 직접 작성 시 오타가 생성도 컴파일에는 문제가 없지만 Criteria로 작성 시 컴파일 시점에서 오류를 발견할 수 있음
- IDE를 사용하면 코드 자동완성 기능 지원 받을 수 있고, 동적 쿼리를 작성하기 편하다.
- 장점도 많지만 복잡도가 높아서 불편한 건 물론이고 가독성도 떨어지는 단점이 있다
10.1.3 QueryDSL 소개
- JPQL을 생성하는 빌더 역할
- 장점은 코드 기반이면서 단순하고 사용하기 쉽다는 점과 JPQL과 비슷해서 한눈에 들어오는 코드 가독성
- JPA 표준은 아니고 오픈소스 프로젝트이며 JPA 뿐만 아니라 몽고 디비 등에도 거의 같은 문법으로 지원한다.
- 현재는 스프링 데이터 프로젝트가 지원하고 있다.
- 어노테이션 프로세서를 사용해서 쿼리 전용 클래스를 만들어야한다 (QClass)
10.1.4 네이티브 SQL 소개
- JPA는 SQL을 직접 사용할 수 있는 기능을 지원하는데 이것이 네이티브 SQL
- JPQL을 사용해도 가끔 특정 디비에 의존하는 기능을 사용해야할 떄 사용 (예 : 오라클 디비에만 사용하는 CONNECT BY 기능이나 SQL 힌트 등)
10.1.5 JDBC 직접 사용, 마이바티스 같은 SQL 매퍼 프레임워크
- JDBC 커넥션에 직접 접근 시 JPA는 제공하지 않아 JPA 구현체가 제공하는 방법을 사용해야함
- JPA 엔티티매니저에서 하이버네이트 Session을 구하고 doWork() 메서드 호출 시 JDBC 커넥션 획득 가능
10.2 JPQL
- JPQL은 객체지향 쿼리 언어 -> 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리
- JPQL은 SQL을 추상화해서 특정 디비 SQL에 의존하지 않음
- JPQL은 결국 SQL로 변환됨
10.2.1 기본 문법과 쿼리API
- JPQL도 SQL과 비슷하게 SELECT, UPDATE, DELETE문 사용 가능
- 저장은 persist() 메서드를 사용하므로 INSERT문은 없음
- SELECT문
- 대소문자 구분 : 엔티티와 속성은 대소문자 구분한다. 반면 JPQL 키워드(SELECT, FROM ..) 등은 대소문자를 구분하지 않음
- 엔티티 이름 : 엔티티 명은 @Entity(name=“”)으로 지정 가능하며 지정 안할 시 클래스명을 기본값으로 사용함. -> 기본값으로 사용하는 것 추천
- 별칭은 필수 : AS 를 사용하여 별칭을 필수로 사용해야 함
- 하이버네이트는 JPQL 표준도 지원하지만 더 많은 기능을 가진 HQL도 제공한다.
- TypeQuery, Query
- 작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 함
- 쿼리 객체에는 TypeQuery와 Query가 있는데 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery 객체를 사용하고 아닐시 Query 객체를 사용
10.2.2 파라미터 바인딩
- JDBC는 위치 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원
- 이름 기준 파라미터
- 이름 기준 파라미터 앞에 :를 사용함
- JPQL API는 대부분 메소드 체인 방식으로 설계되어 있어 체이닝 가능
- 위치 기준 파라미터
- 위치 기준 파라미터를 사용하려면 ? 다음에 위치 값을 주면 됨
- 위치 기준보다는 이름 기준이 더 명확함
10.2.3 프로젝션
- SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라 한다.
- 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기분 데이터 타입)이 있음
- 엔티티 프로젝션
- SELET m FROM Member m 처럼 엔티티를 프로젝션 대상으로 사용하면 컬럼을 하나하나 나열해서 조회해야하는 SQL과는 차이가 있다.
- 임베디드 타입 프로젝션
- JPQL에서 임베디드 타입은 엔티티와 거의 비슷하게 사용됨
- 임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있다.
- 임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 따라서 영속성 컨텍스트에서 관리되지 않는다.
- 스칼라 타입 프로젝션
- 통계쿼리에서 주로 사용
- 여러 값 조회
- 꼭 필요한 데이터들만 선택해서 조회할 때 Query를 사용
- NEW 명령어
- SELECT 다음에 NEW 명령어를 사용하면 반환받을 클래스를 지정할 수 있다.
- 패키지 명을 포함한 전체 클래스 명을 입력해야한다.
- 순서와 타입이 일치하는 생성자가 필요함
10.2.4 페이징 API
- 페이징 처리용 SQL을 작성하는데는 지루하고 반복적이며 디비마다 페이징 처리하는 SQL 문법이 다르다는 문제가 있다.
- 디비마다 다른 페이징 처리를 같은 API로 처리할 수 있는 것은 데이터베이스 방언 덕분
- 페이징 SQL을 더 최적화하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야한다.
10.2.5 집합과 정렬
- 집합 함수
- COUNT
- MAX, MIN
- AVG
- SUM
- 집합 함수 사용 시 참고사항
- NULL 값은 무시하므로 통계에 잡히지 않는다. (DISTINCT도 무시)
- 만약 값이 없는데 SUM, AVG 등을 사용하면 NULL 값이 되며 COUNT는 0이됨
- DISTINCT를 집합 함수 안에 사용해서 중복된 값을 제거하고 나서 집합을 구할 수 있음
- DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않음
- GROUP BY, HAVING
- 통계 쿼리를 사용하면 애플리케이션으로 수십 라인 작성할 코드를 줄여주지만 보통 통계쿼리는 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 크다
- 결과가 많다면 통계 결과만 저장하는 테이블을 별도로 만들어 두고 사용자가 적은 새벽에 통계 쿼리를 실행해서 그 결과를 보관하는 것이 좋다.
- 정렬 (ORDER BY)
- 오름차순, 내림차순 정렬..
10.2.6 JPQL 조인
- 내부 조인
- INNER JOIN 사용
- JPQL 내부 조인은 연관 필드를 사용하는 것이 특징이다.
- 외부 조인
- OUTER는 생략 가능해서 LEFT JOIN으로 사용
- 컬렉션 조인
- 일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것
- 다대일 조인이면서 단일 값 필드 (m.team) 사용
- 반대로 일대다 조인이면서 컬렉션 값 연관 필드 (m.members) 사용
- 컬렉션 조인 시 JOIN 대신에 IN을 사용할 수 있는데 기능상 JOIN과 같지만 컬렉션 일때만 사용 가능함. 하지만 특별한 장점이 없으므로 그냥 JOIN 명령어 사용
- 세타 조인
- WHERE 절을 사용해서 세타 조인 가능
- 세타 조인은 내부 조인만 지원
- 전혀 관계없는 엔티티도 조인 가능
- JOIN ON절 (JPA 2.1)
- JPA 2.1 부터 조인할 때 ON 절을 지원한다.
- ON절을 사용하면 조인 대상을 필터링하고 조인이 가능
- 내부 조인의 ON 절은 WHERE 절을 사용할때와 결과가 같으므로 보통 ON 절은 외부 조인에서만 사용
10.2.7 페치 조인
- 페치 조인은 JPQL에서 성능 최적화를 위해 제공하능 기능
- 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이며 join fetch 명령어로 사용 가능
- 엔티티 페치 조인
- 일반적인 JPQL 조인과는 다르게 페치 조인은 별칭을 사용할 수 없다.
- 하지만 하이버네이트는 페치 조인에도 별칭을 허용한다
- 컬렉션 페치 조인
- 일대다 조인은 결과가 증가할 수 있지만 일대일, 다대일 조인은 결과가 증가하지 않는다.
- 페치 조인과 DISTINCT
- JPQL의 DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한번 더 중복을 제거한다.
- 페치 조인과 일반 조인의 차이
- 일반 조인은 결과를 반환할 때 까지 연관관계를 고려하지 않는다
- 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다
- 즉시 로딩으로 설정 시 회원 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행한다.
- 반면 페치 조인을 사용하면 연관된 엔티티도 함께 조회한다.
- 페치 조인의 특징
- 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함꼐 조회할 수 있어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있다.
- 엔티티에 직접 적용하는 로딩 전략은 애플리케이션 전체에 영향을 미치므로 글로벌 로딩 전략이라 함
- 페치 조인은 글로벌 로딩 전략보다 우선한다.
- 따라서 글로벌 로딩 전략은 될 수 있으면 지연 로딩을 사용하고 최적화가 필요한 곳에 페치 조인을 적용하는 것이 효과적
- 페치 조인의 한계
- 페치 조인 대상에는 별칭을 줄 수 없다. -> 하이버네이트는 가능하지만 별칭 잘못 사용하면 데이터 무결성이 깨질 수 있으므로 조심해서 사용해야함
- 둘 이상의 컬렉션을 페치할 수 없다. -> 구현체에 따라 되기도 하는데 컬렉션 * 컬렉션의 카테시안 곱이 만들어지므로 주의 필요
- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다. -> 단일 값 연관 필드들은 사용 가능
10.2.8 경로 표현식
- 경로 표현식이란 .(점)을 찍어 객체 그래프를 탐색하는 것
- 경로 표현식의 용어 정리
- 상태 필드 : 단순히 값을 저장하기 위한 필드
- 연관 필드 : 연관관계를 위한 필드, 임베디드 타입 포함
- 단일 값 연관 필드 : @ManyToOne, @OneToOne 대상이 엔티티
- 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
- 상태필드는 단순히 값을 저장하는 필드, 연관 필드는 객체 사이의 연관관계를 맺기 위해 사용하는 필드
- 경로 표현식과 특징
- 상태 필드 경로 : 경로 탐색의 끝, 더는 탐색할 수 없다.
- 단일 값 연관 경로 : 묵시적으로 내부조인이 발생, 단일 값 연관 경로는 계속 탐색할 수 있음
- 컬렉션 값 연관 경로 : 묵시적으로 내부조인이 발생, 더는 탐색할 수 없음, 단 FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색 가능
- 여기서 묵시적 조인이란 경로 표현식에 의해 묵시적으로 조인이 일어나는 것을 뜻하며 내부 조인만 가능하다. 명시적 조인은 직접 JOIN을 적어주는 것을 말함
- 컬렉션 값 연관 경로 탐색
- JPQL을 다루면서 많이 하는 실수 중 하나
- 컬렉션에서 경로 탐색을 시작하는 것은 허락하지 않음
- 만약 하고 싶다면 별칭을 따로 주어 사용 가능
- 경로 탐색을 사용한 묵시적 조인 시 주의사항
- 항상 내부 조인이다
- 컬렉션을 경로 탐색의 끝이다. -> 사용시 별칭 사용
- 조인이 성능상 차지하는 부분은 아주 큼 -> 묵시적 조인이 일어나는 상황을 한눈에 파악하기 어려워 성능이 중요할 시 묵시적조인보다는 명시적 조인을 사용하는 것을 권장
10.2.9 서브 쿼리
- JPQL도 서브 쿼리 지원하지만 몇가지 제약이 있음
- WHERE, HAVING 절에서만 사용 가능하고 SELECT, FROM 절에서는 사용 불가능
- 하이버네이트의 HQL은 SELECT 절의 서브 쿼리도 허용, 하지만 아직까지 FROM 절의 서브 쿼리는 지원하지 않음
- 서브 쿼리 함수
- EXISTS
- ALL / ANY / SOME : 비교 연산자와 같이 사용
- IN : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
10.2.11 다형성 쿼리
- TYPE : TYPE은 엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용
- TREAT(JPA 2.1) : 자바의 타입 캐스팅과 비슷함. 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용 JPA 표준은 FROM, WHERE 절에서 사용 가능하지만 하이버네이트는 SELECT 절에서도 사용 가능
10.2.12 사용자 정의 함수 호출(JPA 2.1)
- 하이버네이트 구현체 사용 시 방언 클래스를 상속해서 구현하고 사용할 데이터베이스 함수를 미리 등록해야함
10.2.13 기타 정리
- enum은 = 비교연산자만 지원
- 임베디드 타입은 비교를 지원하지 않음
- EMPTY STRING : JPA 표준은 ‘’을 길이 0인 Empty String으로 정했지만 디비에 따라 ‘’를 NULL로 사용하는 디비도 있으니 확인하고 사용해야 함
- NULL 정의
- 조건 만족하는 데이터가 없으면 NULL
- NULL과의 모든 수학적 계산 결과는 NULL
- NULL == NULL 은 알수 없는 값
- Null is Null은 참
10.2.14 엔티티 직접 사용
- 기본 키 값
- 객체 인스턴스는 참조 값으로 식별, 테이블 로우는 기본 키 값으로 식별
- 따라서 JPQL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 기본 키 값을 사용
10.2.15 Named 쿼리 : 정적 쿼리
- JPQL 쿼리는 크게 동적 쿼리와 정적 쿼리로 나뉨
- 동적 쿼리
- JPQL을 문자로 완성해서 직접 넘기는 것을 동적 쿼리라 함
- 런타임에 특정 조건에 따라 JPQL을 동적으로 구성 가능
- 정적 쿼리
- 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있는데 이것을 Named 쿼리라 함
- 한번 정의하면 변경할 수 없어 정적인 쿼리임
- Named 쿼리는 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해줌
- Named 쿼리를 어노테이션에 정의하여 사용도 가능
- 환경에 따른 설정
- 만약 XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가짐
'Book' 카테고리의 다른 글
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (12) 스프링 데이터 JPA (0) | 2022.11.06 |
---|---|
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (10-2) 객체 지향 쿼리 언어 (0) | 2022.10.30 |
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (9) 값 타입 (0) | 2022.09.18 |
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (8-2) 프록시와 연관관계 관리 (0) | 2022.09.10 |
자바 ORM 표준 JPA 프로그래밍 - JPA 정리하기 (8-1) 프록시와 연관관계 정리 (0) | 2022.08.27 |
댓글