이 학습 자료는 김영한님의 인프런 강의를 참고해서 만들었습니다.
1. 객체지향 쿼리 언어
1.1 JPQL
- JPA에서 SQL을 추상화한 객체 지향 쿼리 언어
- SQL과 문법과 유사
- 테이블이 아닌 엔티티 객체를 대상으로 쿼리
- 검색 조건이 포함된 SQL을 사용해서 필요한 데이터만 DB에서 가져올 수 있음
1.2 Criteria
- 자바 코드로 JPQL 작성 가능
- JPA 공식 기능
- 너무 복잡하고 실용성이 없다.
1.3 QueryDSL
- 오픈소스
- 자바 코드로 JPQL 작성 가능 → 컴파일 시점에 문법 오류 발견
- 동적 쿼리 작성 편리
- 단순하고 쉬움
- 실무 사용 권장
1.4 네이티브 SQL
- JPA가 제공하는 SQL을 직접 사용하는 기능
2. JPQL(Java Persistence Query Language) - 기본문법
- 객체지향 쿼리 언어로 엔티티 객체를 대상으로 한다.
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존X
- JPQL은 SQL로 변환되어서 실행
2.1 JPQL 문법
- ex)
select m from Member as m where m.age > 18
- 엔티티(
Member
)와 속성(age
)는 대소문자 구분 - JPQL 키워드는 대소문자 구분X(
SELECT
,FROM
,where
) - 엔티티 이름을 사용, 테이블 이름X
- 별칭(
m
)은 필수(as
는 생략 가능)
- 엔티티(
- 집합과 정렬
select COUNT(m), //회원수 SUM(m.age), //나이 합 AVG(m.age), //평균 나이 MAX(m.age), //최대 나이 MIN(m.age) //최소 나이 from Member m
GROUP BY
,HAVING
,ORDER BY
다 똑같이 사용 가능
TypeQuery
: 반환 타입이 명확할 때 사용Query
: 반환 타입이 명확하지 않을 때 사용
2.1.1 결과 조회 API
query.getResultList()
: 결과가 하나 이상일 때, 리스트 반환- 결과가 없으면 빈 리스트
query.getSingleResult()
결과가 정확히 하나일 때, 단일 객체 반환- 결과의 수가 다르면 예외 발생(없음 :
NoResultException
, 둘 이상 :NonUniqueResultException
)
- 결과의 수가 다르면 예외 발생(없음 :
2.1.2 파리미터 바인딩 - 이름 기준, 위치 기준
- 위치기반은 사용하면 안된다 → 버그 발생
2.2 프로젝션(SELECT)
SELECT
절에 조회할 대상을 지정하는 것- 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입
-
SELECT m FROM Member m //엔티티 프로젝션(반환되는 엔티티들은 모두 영속성 컨텍스트에서 관리) SELECT m.team FROM Member m //엔티티 프로젝션 SELECT m.address FROM Member m //임베디드 타입 프로젝션(address는 값 타입), 항상 '엔티티.'으로 시작해야함 SELECT /*DISTINCT*/ m.username, m.age FROM Member m //스칼라 타입 프로젝션, DISTINCT로 중복 제거 가능
-
2.2.1 여러 값 조회
ex) SELECT m.username, m.age FROM Member m
- Query 타입으로 조회
- Object[] 타입으로 조회
- new 명령어로 조회 → 가장 깔끔함
- DTO객체를 하나 만들어서 사용
- 패키지 명을 포함한 전체 클래스 명이 필요
- 순서와 타입이 일치하는 생성자 필요
SELECT new Jjpabook.jpql.UserDTO(m.username, m.age) FROM Member m
2.3 페이징 API
- 페이징 API는
setFirstResult(int startPosition)
과setMaxResults(int maxResult)
로 구성 setFirstResult(int startPosition)
: 조회 시작 위치(0부터 시작)setMaxResults(int maxResult)
: 조회할 데이터 수
2.4 조인
- 내부 조인 :
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
- ON 절을 사용한 조인
- ex) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
- 연관관계 없는 엔티티 외부 조인
- ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
- ex) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
1. 조인 대상 필터링
2.5 서브 쿼리
- 일반적인 서브 쿼리와 같다
- ex) 나이가 평균보다 많은 회원
select m from Member m where m.age > (select avg(m2.age) from Member m2)
- ex) 한 건이라도 주문한 고객
select m from Member m where (select count(o) from Order o where m = o.member) > 0
- 서브쿼리의 한계
- JPA는
WHERE
,HAVING
절에서만 서브 쿼리 사용 가능 SELECT
절도 가능(하이버네이트에서 지원)FROM
절의 서브 쿼리는 현재 불가능- 조인으로 풀 수 있으면 풀어서 해결
- JPA는
2.5.1 서브 쿼리 지원 함수
[NOT] EXISTS (subquery)
: 서브쿼리에 결과가 존재하면 참{ALL | ANY | SOME} (subquery)
ALL
: 모두 만족하면 참ANY
,SOME
: 조건 중 하나라도 만족하면 참
[NOT] IN (subquery)
: 서브쿼리 결과 중 하나라도 같은 것이 있으면 참
2.6 JPQL 타입 표현
- 문자
- 숫자
- Boolean
- ENUM :
패키지.클래스명.ENUM
(패키지명 포함) - 엔티티 타입 :
TYPE(m) = Member
(상속 관계에서 사용)
2.7 조건식
2.7.1 CASE 식
- 기본 CASE 식
select case when m.age <= 10 then '학생요금' when m.age >= 60 then '경로요금' else '일반요금' end from Member m
- 단순 CASE 식 → 정확하게 일치해야 함
select case when '팀A' then '인센티브110%' when '팀B' then '인센티브120%' else '인센티브105%' end from Team t
COALESCE
: 하나씩 조회해서 null이 아니면 반환select coalesce(m.username, '이름 없는 회원') from Member m
NULLIF
: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환select NULLIF(m.username, '관리자') from Member m
2.8 JPQL 기본 함수
2.8.1 표준 함수
- CONCAT ,
||
(하이버네이트) : 문자를 합하는 함수 - SUBSTRING : 문자열을 잘라냄
- TRIM : 공백제거
- LOWER, UPPER : 대소문자 변경
- LENGTH : 문자열의 길이
- LOCATE : 지정된 문자(열)의 위치
- ABS, SQRT, MOD : 수학 연산 함수
- SIZE : 컬렉션의 크기
- INDEX(JPA 용도)
2.8.2 사용자 정의 함수
- 하이버네이트는 사용전에 방언에 추가해야 한다
- 사용하는 DB방언을 상속받고, 사용자 정의 함수를 등록한다.
- 함수를 클래스로 등록한 후 방언 추가(
"hibernate.dialect" value="dialect.클래스명"
) - → 함수 사용 :
select function('메서드이름', i.name) from Item i
- 함수를 클래스로 등록한 후 방언 추가(
- 사용하는 DB방언을 상속받고, 사용자 정의 함수를 등록한다.
3. JPQL 중급문법
3.1 경로 표현식
.
(점)을 찍어서 객체 그래프를 탐색하는 것
- 상태 필드 : 단순히 값을 저장하기 위한 필드, 경로 탐색의 끝, 탐색X
- 연관 필드 : 연관관계를 위한 필드
- 단일 값 연관 필드(
@ManyToOne
,@OneToOne
) : 대상이 엔티티, 묵시적 내부 조인 발생, 탐색O - 컬렉션 값 연관 필드(
@OneToMany
,@ManyToMany
) : 대상이 컬렉션, 묵시적 내부 조인 발생, 탐색X- 단
FROM
절로 부터 명시적 조인을 통해 별칭을 얻어서 탐색가능
- 단
- 단일 값 연관 필드(
- 명시적 조인 :
join
키워드 직접 사용 -
select m from Member m join m.team t
- 묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL조인 발생, 항상 내부 조인(한눈에 파악하기 어려움)가급적 명시적 조인만 사용
-
select m.team from Member m
3.2 페치 조인(fetch join)
3.2.1 @ManyToOne
페치 조인
- 연관된 엔티니나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- JPQL에서 성능 최적화를 위해 제공하는 기능으로써 SQL에서는 사용되지 않는다.→ [JPQL]
select m from Member m join fetch m.team
- ex) [SQL]
SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID
- 지연로딩으로 설정해도 페치조인이 우선시 된다.
3.2.2 컬렌션 페치 조인
- 컬렉션으로도 페치 조인이 가능한데, 중복이 발생할 수 있다.→ [JPQL]
select t from Team t join fetch t.members where t.name = ‘팀A'
- ex) [SQL]
SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
DISTINCT
사용 → 일반적인 SQL의DISTINCT
기능 + 애플리케이션에서 엔티티 중복 제거 기능(같은 식별자를 가진 엔티티 제거)
3.2.3 페치 조인과 일반 조인의 차이
- 일반 조인은 연관된 엔티티를 함께 조인하지 않음
- 페치 존인은 연관된 엔티티를 즉시 로딩으로 함께 조회
3.2.4 페치 조인의 특징과 한계
- 특징
- 연관된 엔티티들은 SQL 한 번으로 조회 → 성능 최적화
- 페치 조인 대상에는 별칭을 주면 안된다.
- 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 한계
- 컬렉션은 페치 조인하면 페이징API(
setFirstResult
,setMaxResult
)를 사용할 수 없다.- 단일 값 연관 필드들은 페치 조인을 해도 페이징 가능
- 해결방법
- 일대다를 다대일로 뒤집어서 해결
hibernate.default_batch_fetch_size
적절한 값으로 설정해 놓고 쓴다
- 컬렉션은 페치 조인하면 페이징API(
3.3 다형성 쿼리
- 조회 대상을 특정 자식으로 한정
[SQL]select i from i where i.DTYPE in (‘B’, ‘M’)
→ [JPQL]select i from Item i where type(i) IN (Book, Movie)
TREAT
: 자바의 캐스팅과 유사, 상속 구조에서 부토 타입을 특정 자식 타입으로 다룰 때 사용(FROM
,WHERE
,SELECT
(하이버네이트)에서 사용)
[SQL]select i.* from Item i where i.DTYPE = ‘B’ and i.auther = ‘kim’
→ [JPQL]select i from Item i where treat(i as Book).auther = ‘kim’
3.4 엔티티 직접 사용
3.4.1 기본 키 값
- JPQL에서 아래와 같이 입력하면
select count(m.id) from Member m
//엔티티의 아이디를 사용select count(m) from Member m
//엔티티를 직접 사용
select count(m.id) as cnt from Member m
SQL문으로 변환되어서 실행- 파라미터나 식별자로 전달해도 같이 나온다.
3.4.2 외래 키 값
3.5 Named 쿼리
- 엔티티에 어노테이션이나 XML으로 선언해 놓은 쿼리 → XML이 항상 우선권을 가짐
- 정적 쿼리만 가능
- 애플리케이션 로딩 시점에 초기화 후 재사용 + 쿼리를 검증
- ex)
3.6 벌크 연산
- SQL의
UPDATE
와DELETE
문과 비슷INSERT(insert into .. select)
지원
- 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
- 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 하기 때문에 주의해야 함
- 해결법
- 영속성 컨텍스트에 넣기 전에 벌크 연산을 먼저 실행
- 벌크 연산 수행 후 영속성 컨텍스트 초기화
- 해결법
'ORM > JPA' 카테고리의 다른 글
[JPA] 엔티티의 생명주기와 Spring Data JPA- (1) (0) | 2022.10.08 |
---|---|
[JPA] 영속성 전이와 고아 객체 (0) | 2022.07.18 |
자바 ORM 표준 JPA 프로그래밍 - 값 타입 (0) | 2021.08.21 |
자바 ORM 표준 JPA 프로그래밍 - 프록시와 연관관계 관리 (0) | 2021.08.19 |
자바 ORM 표준 JPA 프로그래밍 - 고급 매핑 (0) | 2021.08.16 |