1. 객체지향 쿼리 소개
•
EntityManager.find() 메소드를 사용하면 식별자로 엔티티 하나를 조회할 수 있다.
◦
식별자로 조회 EntityManager.find()
◦
객체 그래프 탐색
•
현실의 애플리케이션은 복잡하기 때문에
•
•
ORM을 사용하면 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 엔티티 객체를 대상으로 하는 방법이 필요하다. JPQL은 이런 문제를 해결하기 위해 다음과 같은 특징이 있다.
•
테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리다
•
SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다
•
다음은 JPA가 공식 지원하는 기능이다.
◦
JPQL
◦
Criteria 쿼리 : JPQL을 편하게 작성하도록 도와주는 API, 빌더 클래스 모음
◦
네이티브 SQL : JPA에서 JPQL 대신 직접 SQL을 사용할 수 있다.
2. JPQL
•
JPQL의 특징
◦
JPQL은 객체지향 쿼리 언어로 테이블을 대상으로 쿼리하는 것이 아닌 엔티티 객체를 대상으로 쿼리한다.
◦
JPQL은 SQL을 추상화해 특정 데이터베이스 SQL에 의존하지 않는다.
◦
JPQL은 결국 SQL로 변환된다.
2.1. 기본 문법과 쿼리 API
•
JPQL도 SQL과 비슷하게 SELECT, UPDATE, DELETE 문을 사용할 수 있다.
2.1.1. SELECT 문
SELECT m FROM Member AS m WHERE m.username = 'Hello'
SQL
복사
•
대소문자 구분
◦
엔티티와 속성은 대소문자를 구분한다.
•
엔티티 이름
◦
JPQL에서 사용하는 객체는 클래스 명이 아니라 엔티티 명이다.
◦
엔티티 명은 @Entity(name = "xxx")로 지정할 수 있다.
•
별칭은 필수
◦
JPQL은 별칭은 필수로 사용해야 한다.
2.1.2. TypeQuery, Query
•
JPQL을 실행하려면 쿼리 객체를 만들어야 한다.
•
쿼리 객체는 TypeQuery와 Query가 있다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Java
복사
반환할 타입을 명확하게 TypeQuery 객체를 사용
Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
Java
복사
반환할 타입을 명확히 지정할 수 없으면 Query 객체를 사용
2.1.3. 결과 조회 API
•
다음 메소드들을 호출하면 실제 쿼리를 실행해서 데이터베이스를 조회한다.
•
query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
◦
결과가 없으면 빈 리스트 반환
•
query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환
◦
결과가 없으면 : javax.persistence.NoResultException
◦
둘 이상이면 : javax.persistence.NonUniqueResultException
2.2. 파라미터 바인딩
•
JDBC는 위치 기준 기준 파라미터 바인딩만 지원하지만 JPQL은 이름 기준 파라미터 바인딩도 지원한다.
•
이름 기준 파라미터
◦
이름 기준 파라미터는 파라미터를 이름으로 구분하는 방법이다.
◦
이름 기준 파라미터는 앞에 :를 사용한다.
•
위치 기준 파라미터
◦
위치 기준 파라미터를 사용하려면 ?다음에 위치 값을 주면된다.
•
위치 기준 파라미터 방식보다는 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확하다.
2.3. 프로젝션
•
SELECT 절에 조죄할 대상을 지정하는 것을 프로젝션이라 하고 SELECT {프로젝션 대상} FROM으로 대상을 선택한다.
•
프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다.
•
엔티티 프로젝션
◦
SELECT m FROM Member m
◦
SELECT m.team FROM Member m
•
임베디드 타입 프로젝션
◦
SELECT m.address FROM Member m
•
스칼라 타입 프로젝션
◦
SELECT m.username, m.age FROM Member m
•
여러 값 조회
◦
꼭 필요한 데이터들만 선택해 조회해야할 경우 Query를 사용해야 한다.
◦
em.createQuery("SELELCT m.username, m.age FROM Member m");
•
NEW 명령어
◦
의미 있는 객체로 리턴받고 싶을 경우 이용한다.
◦
SELECT new UserDTO(m.username, m.age) FROM Member m
◦
사용시 주의사항
▪
패키지 명을 포함한 전체 클래스 명 입력
▪
순서와 타입이 일치하는 생성자 필요
2.4. 페이징 API
•
JPA는 페이징을 두 API로 추상화 했다.
◦
setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
◦
setMaxResults(int maxResult) : 조회할 데이터 수
TypedQuery<Member> query = em.createQuery("select m from Member m order by m.age desc", Member.class);
query.setFirstResult(0)
query.setMaxResults(10)
query.getResultList();
Java
복사
•
JPA는 방언 덕분에 각 데이터베이스별로 손쉽게 대응이 가능하다.
•
페이징 SQL을 더 최적화 하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야 한다.
2.5. 집합과 정렬
•
집합은 집합함수와 함께 통계 정보를 구할 때 사용한다.
2.5.1. 집합 함수
2.5.2. 집합 함수 사용 시 참고사항
•
NULL 값은 무시하므로 통계에 잡히지 않는다.
◦
DISTINCT가 정의되어 있어도 무시된다.
•
만약 값이 없는데 SUM, AVG, MAX, MIN 함수를 사용하면 NULL 값이 된다.
◦
단 COUNT는 0이 된다.
•
DISTINCT를 집합 함수 안에 사용해서 중복된 값을 제거하고 나서 집합을 구할 수 있다.
◦
ex) select COUNT(DISTINCT m.age) from Member m
•
DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.
2.5.3. GROUP BY, HAVING
•
GROUP BY는 통계 데이터를 구할 때 특정 그룹끼리 묶어준다.
select t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name
SQL
복사
•
HAVING은 GROUP BY와 함께 사용하는데 GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링한다.
select t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
from Member m LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 10
SQL
복사
•
이런 쿼리들을 보통 리포팅 쿼리나 통계 쿼리라 한다.
•
이러한 통계 쿼리를 잘 활용하면 애플리케이션으로 수십 라인을 작성할 코드도 단 몇 줄이면 처리할 수 있다.
◦
But. 통계 쿼리는 보통 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 많다.
2.5.4. 정렬(ORDER BY)
•
ORDER BY는 결과를 정렬할 때 사용한다.
select m from Member m order by m.age DESC, m.username ASC
SQL
복사
나이를 기준으로 내림차순으로 정렬하고 나이가 같으면 이름을 기준으로 오름차순으로 정렬
•
ASC : 오름차순(기본값)
•
DESC : 내림차순
2.6. JPQL 조인
•
JPQL도 조인을 지원하는데 SQL 조인과 기능은 같고 문법만 약간 다르다.
2.6.1. 내부 조인
•
내부 조인은 INNER JOIN을 사용한다.
String teamName = "팀A";
String query = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("teamName", teamName)
.getResultList();
Java
복사
•
JPQL 내부 조인을 보면 SQL의 조인과 약간 다른 것을 확인할 수 있다.
◦
JPQL 조인의 가장 큰 특징은 연관 필드를 사용한다는 것이다.
•
FROM Member m : 회원을 선택하고 m이라는 별칭을 주었다.
•
Member m JOIN m.team t : 회원이 가지고 있는 연관 필드로 팀과 조인하고 조인한 팀에는 t라는 별칭을 주었다.
•
혹시라도 JPQL 조인을 SQL 조인처럼 사용하면 문법 오류가 발생한다.
2.6.2. 외부 조인
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
SQL
복사
•
외부 조인은 기능상 SQL의 외부 조인과 같다.
◦
OUTER는 생략 가능해서 보통 LEFT JOIN으로 사용한다.
2.6.3. 컬렉션 조인
•
일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라 한다.
•
[회원 -> 팀]으로의 조인은 다대일 조인이면서 단일 값 연관 필드(m.team)를 사용한다.
•
[팀 -> 회원]은 반대로 일대다 조인이면서 컬렉션 값 연관 필드(m.members)를 사용한다.
SELECT t, m FROM Team t LEFT JOIN t.members m
SQL
복사
•
팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인했다.
2.6.4. 세타 조인
•
WHERE 절을 사용해서 세타 조인을 할 수 있다. 참고로 세타 조인은 내부 조인만 지원한다.
•
세타 조인을 사용하면 아래 예제처럼 전혀 관계없는 엔티티도 조인할 수 있다.
// JPQL
select count(m) from Member m, Team t where m.username = t.name
// SQL
SELECT COUNT(M.ID)
FROM
MEMBER M CROSS JOIN TEAM T
WHERE
M.USERNAME=T.NAME
SQL
복사
2.6.5. JOIN ON 절(JPA 2.1)
•
JPA 2.1부터 조인할 때 ON 절을 지원한다.
•
ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있다.
◦
참고로 내부 조인의 ON 절은 WHERE 절을 사용할 때와 결과가 같으므로 보통 ON 절은 외부 조인에서만 사용한다.
// JPQL
select m,t from Member m left join m.team on t.name = 'A'
// SQL
SELECT m.*, t.* FROM Member m
LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'
SQL
복사
2.7. 페치 조인
•
페치(fetch) 조인은 SQL에서 이야기하는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능이다.
•
이것은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능인데 join fetch 명령어로 사용할 수 있다.
◦
페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
2.7.1. 엔티티 페치 조인
select m from Member m join fetch m.team
// 실제 실행
SELECT
M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
SQL
복사
페치 조인을 사용해 연관된 엔티티도 조회하는 JPQL
•
예제를 보면 join 다음에 fetch라 적었다.
•
이렇게 하면 연관된 엔티티나 컬렉션을 함께 조회하는데 여기서는 회원(m)과 팀(m.team)을 함께 조회한다.
엔티티 조인 시도
엔티티 페치 조인 결과 테이블
엔티티 페치 조인 결과 객체
•
엔티티 페치 조인 JPQL에서 select m으로 회원 엔티티만 선택했는데 실행된 SQL을 보면 SELECT M.*, T.*로 회원과 연관된 팀도 함께 조회된 것을 확인할 수 있다.
•
회원을 조회할 때 페치 조인을 사용해서 팀도 함께 조회했으므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티다.
2.7.2. 컬렉션 페치 조인
select t from Team t join fetch t.members where t.name = '팀A'
// 실제 실행
SELECT
T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID = M.TEAM_ID
WHERE T.NAME = '팀A'
SQL
복사
•
컬렉션을 페치 조인한 JPQL에서 select t로 팀만 선택했는데 실행된 SQL을 보면 T.*, M.*로 팀과 연관된 회원도 함께 조회한 것을 확인할 수 있다.
•
일대다 조인은 결과가 증가할 수 있지만 일대일, 다대일 조인은 결과가 증가하지 않는다.
2.7.3. 페치 조인과 DISTINT
•
SQL의 DISTINCT는 중복된 결과를 제거하는 명령이다.
•
JPQL의 DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복을 제거한다.
select distinct t
from Team t join fetch t.members
where t.name = '팀A'
SQL
복사
2.7.4. 페치 조인과 일반 조인의 차이
select t
from Team t join t.members m
where t.name = '팀A'
// 실제 실행
SELECT
T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID = M.TEAM_ID
WHERE T.NAME = '팀A'
SQL
복사
내부 조인 JPQL
•
위의 예제의 JPQL에서 팀과 회원 컬렉션을 조인했으므로 회원 컬렉션도 함께 조회할 것으로 기대해선 안 된다.
◦
실행된 SQL의 SELECT 절을 보면 팀만 조회하고 조인했던 회원은 전혀 조회하지 않는다.
•
JPQL은 결과를 반환할 때 연관관계까지 고려하지 않는다.
◦
단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다.
•
따라서 팀 엔티티만 조회하고 연관된 회원 컬렉션은 조회하지 않는다.
◦
만약 회원 컬렉션을 지연 로딩으로 설정하면 아래 그림처럼 프록시나 아직 초기화되지 않은 컬렉션 래퍼를 반환한다.
◦
즉시 로딩으로 설저하면 회원 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행한다.
2.7.5. 페치 조인의 특징과 한계
•
페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 있어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있다.
•
다음처럼 엔티티에 직접 적용하는 로딩 전략은 애플리케이션 전체에 영향을 미치므로 글로벌 로딩 전략이라 부른다.
◦
페치 조인은 글로벌 로딩 전략보다 우선한다.
•
최적화를 위해 글로벌 로딩 전략을 즉시 로딩으로 설정하면 애플리케이션 전에체서 항상 즉시 로딩이 일어난다.
◦
물론 일부는 빠를 수는 있지만 전체로 보면 사용하지 않는 엔티티를 자주 로딩하므로 오히려 성능에 악영향을 미칠 수 있다.
◦
따라서 글로벌 로딩 전략은 될 수 있으면 지연 로딩을 사용하고 최적화가 필요하면 페치 조인을 적용하는 것이 효과적이다.
•
페치 조인을 사용하면 연관된 엔티티를 쿼리 시점에 조회하므로 지연 로딩이 발생하지 않는다.
◦
따라서 준영속 상태에서도 객체 그래프를 탐색할 수 있다.
•
페치 조인은 다음과 같은 한계가 있다.
◦
페치 조인 대상에는 별칭을 줄 수 없다.
▪
SELECT, WHERE 절, 서브 쿼리에 페치 조인 대상을 사용할 수 없다.
▪
JPA 표준에서는 지원하지 않지만 하이버네이트를 포함한 몇몇 구현체들은 페치 조인에 별칭을 지원한다.
•
하지만 별칭을 잘못 사용하면 연관된 데이터 수가 달라져서 데이터 무결성이 깨질 수 있으므로 조심해서 사용해야 한다.
◦
둘 이상의 컬렉션을 페치할 수 없다.
▪
구현체에 따라 되기도 하는데 컬렉션 * 컬렉션의 카테시안 곱이 만들어지므로 주의해야 한다.
◦
컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
▪
컬렉션(일대다)이 아닌 단일 값 연관 필드(일대일, 다대일)들은 페치 조인을 사용해도 페이징 API를 사용할 수 있다.
▪
하이버네이트에서 컬렉션을 페치 조인하고 페이징 API를 사용하면 경고 로그를 남기면서 메모리에서 페이징 처리를 한다.
•
데이터가 적으면 상관없겠지만 데이터가 많으면 성능 이슈와 메모리 초과 예외가 발생할 수 있어서 위험하다.
◦
엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 억지로 페치 조인을 사용하기보다는 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적일 수 있다.
2.8. 경로 표현식
•
경로 표현식이라는 것은 쉽게 이야기해서 .(점)을 찍어 객체 그래프를 탐색하는 것이다.
select m.username
from Member m
join m.team t
join m.orders o
where t.name = '팀A'
SQL
복사
•
여기서 m.username, m.team, m.orders, t.name이 모두 경로 표현식을 사용한 예다.
2.8.1. 경로 표현식의 용어 정리
•
상태 필드(state field)
◦
단순히 값을 저장하기 위한 필드(필드 or 프로퍼티)
•
연관 필드(association field)
◦
연관관계를 위한 필드, 임베디드 타입 포함(필드 or 프로퍼티)
◦
단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
◦
컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
2.8.2. 경로 표현식과 특징
•
상태 필드 경로
◦
경로 탐색의 끝이다. 더는 탐색할 수 없다.
select m.username, m.age from Member m
SQL
복사
•
단일 값 연관 경로
◦
묵시적으로 내부 조인이 일어난다.
◦
단일 값 연관 경로는 계속 탐색할 수 있다.
select o.member from Order o
SQL
복사
•
컬렉션 값 연관 경로
◦
묵시적으로 내부 조인이 일어난다. 더는 탐색할 수 없다.
▪
단 FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.
select t.members from Team t // 성공
select t.members.username from Team t // 실패
SQL
복사
2.8.3. 경로 탐색을 사용한 묵시적 조인 시 주의사항
•
경로 탐색을 사용하면 묵시적 조인이 발생해서 SQL에서 내부 조인이 일어날 수 있다.
•
이때의 주의사항
◦
항상 내부 조인이다.
◦
컬렉션은 경로 탐색의 끝이다.
▪
컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 한다.
◦
경로 탐색은 주로 SELECT, WHERE(다른 곳에서도 사용됨)에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 준다.
•
조인이 성능상 차지하는 부분은 아주 크다.
•
묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는 단점이 있다.
•
따라서 단순하고 성능에 이슈가 없으면 크게 문제가 안 되지만 성능이 중요하면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자.
2.9. 서브 쿼리
•
JPQL도 SQL처럼 서브 쿼리를 지원한다.
•
여기에는 몇 가지 제약이 있는데, 서브 쿼리를 WHERE, HAVING 절에서만 사용할 수 있고 SELECT, FROM 절에서는 사용할 수 없다.
2.9.1. 서브 쿼리 함수
•
[NOT] EXISTS(subquery)
◦
문법 : [NOT] EXISTS (subquery)
◦
설명 : 서브 쿼리에 결과가 존재하면 참이다. NOT은 반대
select m from Member m where exists (select t from m.team t where t.name = '팀A')
SQL
복사
•
{ALL | ANY | SOME}(subquery)
◦
문법 : {ALL | ANY | SOME} (subquery)
◦
설명 : 비교 연산자와 같이 사용한다. {= | > | >= | < | <= | <>}
▪
ALL : 조건을 모두 만족하면 참이다.
▪
ANY 혹은 SOME : 둘은 같은 의미다. 조건을 하나라도 만족하면 참이다.
select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
select m from Member m where m.team = ANY (select t from Team t)
SQL
복사
•
[NOT] IN (subquery)
◦
문법 : [NOT] IN (subquery)
◦
설명 : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다. 참고로 IN은 서브쿼리가 아닌 곳에서도 사용한다.
select t from Team t where t IN (select t2 FROM Team t2 JOIN t2.members m2 where m2.age >= 20)
SQL
복사
2.10. 조건식
2.10.1. 타입 표현
종류 | 설명 | 예제 |
문자 | 작은 따옴표 사이에 표현작은 따옴표를 표현하고 싶으면 작은 따옴표 연속 두 개('') 사용 | 'HELLO''She''s' |
숫자 | L(Long 타입 지정)D(Double 타입 지정)F(Float 타입 지정) | 10L10D10F |
날짜 | DATE{d'yyyy-mm-dd'}TIME{t'hh-mm-ss'}DATETIME{ts'yyyy-mm-dd hh:mm:ss:f'} | {d'2012-03-24'}{t'10-11-11'}{ts'2012-03-24 10-11-11.123'}m.createDate={d'2012-03-24'} |
Boolean | TRUE,FALSE | |
Enum | 패키지명을 포함한 전체 이름을 사용해야 한다. | jpabook.MemberType.Admin |
엔티티 타입 | 엔티티 타입을 표현한다. 주로 상속과 관련해서 사용한다. | TYPE(m) = Member |
2.10.2. 연산자 우선 순위
1.
경로 탐색 연산 (.)
2.
수학 연산 : +, -(단항 연산자), *, /, +, -
3.
비교 연산 : =, >, >=, <, <=, <>(다름), [NOT] BETWEEN, [NOT] LIKE, [NOT] INT, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF], [NOT] EXISTS
4.
논리 연산 : NOT, AND, OR
2.10.3. 논리 연산과 비교식
•
논리 연산
◦
AND : 둘 다 만족하면 참
◦
OR : 둘 중 하나만 만족해도 참
◦
NOT : 조건식의 결과 반대
•
비교식
◦
= | > | >= | < | <= | <>
2.10.4. Between, IN, Like, NULL 비교
•
Between 식
◦
문법 : X [NOT] BETWEEN A AND B
•
IN 식
◦
문법 : X [NOT] IN(예제)
•
Like 식
◦
문법 : 문자표현식 [NOT] LIKE 패턴값 [ESCAPE 이스케이프문자]
•
NULL 비교식
◦
문법 : {단일값 경로 | 입력 파라미터} IS [NOT] NULL
2.10.5. 컬렉션 식
•
컬렉션 식은 컬렉션에만 사용하는 특별한 기능이다. 참고로 컬렉션은 컬렉션 식 이외에 다른 식은 사용할 수 없다.
•
빈 컬렉션 비교 식
◦
문법 : {컬렉션 값 연관 경로} IS [NOT] EMPTY
select m from Member m where m.orders is not empty
SQL
복사
•
컬렉션의 멤버 식
◦
문법 : {엔티티나 값} [NOT] MEMBER [OF] {컬렉션 값 연관 경로}
select t from Team t where :memberParam member of t.members
SQL
복사
2.11. 다형성 쿼리
•
JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회한다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
...
private String author;
}
// Album, Movie 생략
Java
복사
다형성 쿼리 엔티티
•
단일 테이블 전략(InheritanceType.SINGLE_TABLE)을 사용할 때 실행되는 SQL은 다음과 같다.
SELECT * FROM ITEM
SQL
복사
•
조인 전략(InheritanceType.JOINED)을 사용할 때 실행되는 SQL은 다음과 같다.
SELECT
i.ITEM_ID, i.DTYPE, i.name, i.price, i.stockQuantity,
b.author, b.isbn,
a.artist, a.etc,
m.actor, m.director
FROM
Item i
left outer join
Book b on i.ITEM_ID=b.ITEM_ID
left outer join
Album a on i.ITEM_ID=a.ITEM_ID
left outer join
Movie m on i.ITEM_ID=m.ITEM_ID
SQL
복사
2.11.1. TYPE
•
TYPE은 엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용한다.
// JPQL
select i from Item i
where type(i) IN (Book, Movie)
// SQL
SELECT i FROM Item i
WHERE i.DTYPE in ('B', 'M')
SQL
복사
2.11.2. TREAT(JPA 2.1)
•
TREAT는 JPA 2.1에 추가된 기능인데 자바의 타입 캐스팅과 비슷하다.
•
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
•
JPA 표준은 FROM, WHERE 절에서 사용할 수 있지만 하이버네이트는 SELECT 절에서도 TREAT를 사용할 수 있다.
// JPQL
select i from Item i where treat(i as Book).author = 'kim'
// SQL
select i.* from Item i
where
i.DTYPE='B'
and i.author='kim'
SQL
복사
2.14. 엔티티 직접 사용
2.14.1. 기본 키 값
•
객체 인스턴스는 참조 값으로 식별하고 테이블 로우는 기본 키 값으로 식별한다.
•
따라서 JPQL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 기본 키 값을 사용한다.
select count(m.id) from Member m // 엔티티의 아이디를 사용
select count(m) from Member m // 엔티티를 직접 사용
SQL
복사
•
count(m)을 보면 엔티티의 별칭을 직접 넘겨주었지만 엔티티를 직접 사용하면 JPQL이 SQL로 변환될 때 해당 엔티티의 기본 키를 사용한다.
2.14.2. 외래 키 값
Team team = em.find(Team.class, 1L);
String qlString = "select m from Member m where m.team = :team";
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();
Java
복사
특정 팀에 소속된 회원을 찾는 코드
String qlString = "select m from Member m where m.team.id = :teamId";
List resultList = em.createQuery(qlString)
.setParameter("teamId", 1L)
.getResultList();
Java
복사
엔티티 대신 식별값 사용
select m.*
from Member m
where m.team_id=? (팀 파라미터의 ID 값)
SQL
복사
실제 실행된 SQL
•
m.team.id를 보면 Member와 Team 간에 묵시적 조인이 일어날 것 같지만 MEMBER 테이블이 team_id 외래 키를 가지고 있으므로 묵시적 조인은 일어나지 않는다.
◦
물론 m.team.name을 호출하면 묵시적 조인이 일어난다.
◦
따라서 위 두 코드는 모두 같은 SQL이 생성된다.
2.15. Named 쿼리 : 정적 쿼리
•
JPQL 쿼리는 크게 동적 쿼리와 정적 쿼리로 나눌 수 있다.
◦
동적 쿼리
▪
em.createQuery("select ..")처럼 JPQL을 문자로 완성해도 직접 넘기는 것을 동적 쿼리라 한다.
▪
런타임에 특정 조건에 따라 JPQL을 동적으로 구성할 수 있다.
◦
정적 쿼리
▪
미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있는데 이것을 Named 쿼리라 한다.
▪
Named 쿼리는 한 번 정의하면 변경할 수 없는 정적인 쿼리다.
•
Named 쿼리는 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해 둔다.
◦
오류를 빨리 확인할 수 있고, 사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이점도 있다.
◦
정적 SQL이 생성되므로 데이터베이스의 조회 성능 최적화에도 도움이 된다.
•
Named 쿼리는 @NamedQuery 어노테이션을 사용해서 자바 코드에 작성하거나 또는 XML 문서에 작성할 수 있다.
2.15.1. Named 쿼리를 어노테이션에 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {
...
}
Java
복사
List<Member> resultLIst = em.createNamedQuery("Member.findUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
Java
복사
•
@NamedQuery.name에 쿼리 이름을 부여하고 @NamedQuery.query에 사용할 쿼리를 입력한다.
@Entity
@NamedQueries({
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"),
@NamedQuery(
name = "Member.count",
query = "select count(m) from Member m")
})
public class Member {...}
Java
복사
하나의 엔티티에 2개의 Named쿼를 정의하는 방법
@Target({TYPE})
public @interface NamedQuery {
String name(); // Named 쿼리 이름 (필수)
String query(); // JPQL 정의 (필수)
LockModeType lockMode() default NONE; // 쿼리 실행 시 락모드를
// 설정할 수 있다.
QueryHint[] hints() default {}; // JPA 구현체에 쿼리 힌트를 줄 수 있다.
Java
복사
@NamedQuery 어노테이션
•
lockMode : 쿼리 실행 시 락을 건다.
•
hints : 여기서 힌트는 SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트다. 예를 들어 2차 캐시를 다룰 때 사용한다.
2.15.2. Named 쿼리를 XML에 정의
•
Named 쿼리를 작성할 때는 XML을 사용하는 것이 더 편리하다.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
version="2.1">
<named-query name="Member.findByUsername">
<query><CDATA[
select m
from Member m
where m.username = :username
]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
XML
복사
•
XML에서 &, <, >는 XML 예약문자다.
◦
대신에 &, < >를 사용해야 한다.
◦
<![CDATA[]]>를 사용하면 그 사이에 문장을 그대로 출력하므로 예약문자도 사용할 수 있다.
•
그리고 정의한 ormMember.xml을 인식하도록 META-INF/persistence.xml에 다음 코드를 추가해야 한다.
<persistence-unit name="jpabook">
<mapping-file>META-INF/ormMember.xml</mapping-file>
...
XML
복사
•
META-INF/orm.xml은 JPA가 기본 매핑파일로 인식해서 별도의 설정을 하지 않아도 된다.
2.15.3. 환경에 따른 설정
•
만약 XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가진다.
•
애플리케이션이 운영 환경에 따라 다른 쿼리를 실행해야 한다면 각 환경에 맞춘 XML을 준비해두고 XML만 변경해서 배포하면 된다.