본문 바로가기
Spring/JPA

#10 객체지향 쿼리 언어

by 히포파타마스 2021. 9. 1.

객체지향 쿼리 언어

 

JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리 기술을 지원한다.

 

JPA에서 사용할 수 있는 쿼리 기술은 다음과 같다.

 

● JPQL

● Criteria

● QureyDSL

● 네이티브 SQL

 

JPQL은 가장 중요한 객체지향 쿼리 언어다.

Critera나 QureyDSL은 결국 JPQL을 편리하게 사용하도록 도와주는 기술이므로 JPQL 학습은 필수적이다.

 

 

 

 

1. JPQL 소개

JPQL의 특징은 다음과 같다.

 

● JPQL은 객체지향 쿼리 언어다.

◎ 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.

 

● JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.

 

● JPQL은 결국 SQL로 변환된다.

 

JPQL의 기본 문법은 SQL과 유사하다.

 

[JPQL의 기본 문법 구조]

 

SELECT 문은 다음과 같이 사용한다.

 

[SELECT 절 사용 예시]

"select m from Member as m where m.username = 'Hello'"

● 대소문자 구분

◎ 엔티티와 속성은 대소문자를 구분한다.

ex) Member, username

 

● 엔티티 이름

◎ JPQL에서 사용한 Member는 클래스 명이 아니라 엔티티 명(@Entity(name="XXX")로 지정하는 이름)이다.

◎ 엔티티 명을 지정하지 않으면 클래스 명을 기본값으로 사용한다.

 

● 별칭은 필수이다.

◎ Member as 처럼 별칭을 주어야 한다.

◎ as는 생략할 수 있다.

 

 

COUNT, SUM과 같은 기본적인 함수들도 사용 가능하다.

 

[JPQL 기본 함수]

select
	COUNT(m),   //회원 수
	SUM(m.age), //나이 합
	AVG(m.age), //평균 나이
	MAX(m.age), //최대 나이
	MIN(m.age)  //최소 나이
from Member m

 

 

 

■ TypeQuery, Query

작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다.

쿼리 객체는 TypeQuery와 Query가 있는데 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery 객체를 사용하고, 반환 타입을 명확하게 지정할 수 없으면 Query 객체를 사용하면 된다.

 

[TypeQuery, Query 사용 예]

TypedQuery<Member> query = 
		em.createQuery("select m from Member m", Member.class);
        
Query<Member> query = 
		em.createQuery("select m from Member m");

 

select 절에서 여러 엔티티나 컬럼을 선택할 때도 반환할 타입이 명확하지 않으므로 Query 객체를 사용해야 한다.

※ Query 객체는 select 절의 조회 대상이 둘 이상이면 Object[]를 반환하고 하나라면 Object를 반환한다.

 

 

 

■ 결과 조회

다음 메서드들을 호출하면 실제 쿼리를 실행해서 데이터베이스를 조회한다.

 

● query.getResultList()

◎ 결과를 컬렉션으로 반환한다.

◎ 만약 결과가 없으면 빈 컬렉션을 반환한다.

 

● query.getSingleResult()

◎ 결과가 정확히 하나일 때 사용한다.

◎ 결과가 없으면 NoResultException 예외가 발생한다.

◎ 결과가 1개보다 많으면 NonUniqueResultException 예외가 발생한다.

 

 

 

■ 파라미터 바인딩

[파라미터 바인딩 - 이름 기준]

em.createQuery("select m from Member m where m.username = :username", Member.class)
		.setParameter(1, usernameParam)
		.getResultList();

이름 기준 파라미터는 파라미터를 이름으로 구분하는 방법이다.

이름 기준 파라미터는 앞에 :를 사용한다.

 

[파라미터 바인딩 - 위치 기준]

em.createQuery("select m from Member m where m.username=?1", Member.class)
		.setParameter(1, usernameParam)
		.getResultList();

위치 기준으로 파라미터를 사용하려면 ? 다음에 위치 값을 주면 된다.

위치 값은 1부터 시작한다.

 

위치 기준 파라미터 방식보다는 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확하다.

 

 

 

■ 프로젝션 

select 절에 조회할 대상을 지정하는 것을 프로젝션(projection)이라 한다.

프로젝션 대상은 엔티티, 엠비디드 타입, 스칼라 타입이 있다.

 

프로젝션에 여러 값을 선택하면 TypeQuery를 사용할 수 없고 대신에 Query를 사용해야 한다.

 

[여러 프로젝션 조회]

List<Object[]> resultList = 
	em.createQuery("select m.username, m.age from member m")
			.getResultList();
    
for (Object[] row : resultList) {
	String username = (String) row[0];
	Integer age = (Integer) row[1];
}

조회된 값은 Object[]에 순서대로 나열되고 조회된 값이 여러 개면 List로 조회할 수 있다.

 

실제 어플리케이션 개발 시에는 Object[]를 직접 사용하지 않고 DTO로 변환해서 사용할 것이다.

 

[NEW 명령어 사용] 

TypedQuery<UserDTO> query =
		em.createQuery("select new jpabook.jpql.UserDTO(m.username, m.age)
		from Member m", UserDTO.class);
        
List<UserDTO> resultList = query.getResultList();

select 다음에 NEW 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JPQL 조회 결과를 넘겨줄 수 있다.

※ 클래스는 패키지명을 포함한 전체 클래스 명을 입력해야 한다.

 

 

 

■ 페이징 API

JPA는 페이징을 다음 두 API로 추상화했다.

 

● setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)

● setMaxResults(int maxResult) : 조회할 데이터 수

 

[페이징 사용]

TypedQuery<Member> query =
		em.createQuery("select m from Member m ORDER BY m.username DESC", Member.class);
			.setFirstResult(10); //11번째 부터 시작
			.setMaxResults(20);  //20개 반환
			.getResultList();

위 예제는 조회된 결과를 11번째부터 시작해서 총 20건의 데이터를 조회한다.

 

 

 

■ GROUP BY, HAVING, ORDER BY

GROUP BY 는 통계 데이터를 구할 때 특정 그룹끼리 묶어준다.

 

[GROUP BY 예시]

select t.name, COUNT(m.age), SUM(m.age), AVG(m.age)
from Member m left join m.team t
group by t.name

모든 회원을 대상으로 해서, 팀 이름 기준으로 그룹별로 묶어서 통계 데이터를 구하였다.

 

HAVING 은 GROUP BY 와 함께 사용하는데 GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링한다.

 

[HAVING 예시]

select t.name, COUNT(m.age), SUM(m.age), AVG(m.age)
from Member m left join m.team t
group by t.name
having avg(m.age) >= 10

 

 

ORDER BY 는 결과를 정렬할 때 사용한다.

 

[ORDER BY 예시]

select m from Member m order by m.age DESC, m.username ASC

위 예시는 나이를 기준으로 내림차순으로 정렬하고 나이가 같으면 이름을 기준으로 오름차순으로 정렬한다.

● ACS : 오름차순(기본값)

● DESC : 내림차순

 

 

 

■ 서브 쿼리

JPQL은 서브쿼리 사용이 지원된다.

 

[서브 쿼리 예제]

select m from Member m
where m.age > (select avg(m2.age) from Member m2)

위 예제는 나이가 평균보다 만은 Member를 조회한다.

 

서브 쿼리를 지원하는 함수는 다음과 같다.

 

● [NOT] EXISTS (subquery)

◎ 서브쿼리에 결과가 존재하면 참

 

● {ALL | ANY | SOME} (subquery)

◎ ALL : 모두 만족하면 참

◎ ANY, SOME : 조건을 하나라도 만족하면 참

 

● [NOT] IN (subquery)

◎ 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

 

[서브 쿼리 지원 함수 예시]

//팀A 소속인 회원
select m from Member m
where exists(select t from m.team t where t.name = '팀A')

//전체 상품 각각의 재고보다 주문량이 많은 주문들
selecct 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)

 

 

□ JPA 서브 쿼리 한계

 

● JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능

◎ 하이버네이트에서는 SELECT 절도 가능하다.

 

● FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다.

◎ 조인으로 풀 수 있으면 풀어서 해결하자.

 

 

 

■ JPQL 타입 표현

● 문자

◎ 'HELLO'

◎ 작은따옴표 사이에 표현하려면 작은 따옴표 연속 사용

ex) 'She''s'

 

● 숫자

◎ 10L(Long), 10D(Double), 10F(Float)

● Boolean

◎ TRUE, FALSE

 

● ENUM

◎ 패키지명을 포함해야 한다.

ex) jpabook.MemberType.Admin

 

● 엔티티 타입

◎ TYPE(m) = Member (상속 관계에서 사용)

 

 

 

■ JPQL 기타

SQL과 문법이 같은 식은 다음과 같다.

 

● EXISTS, IN

● AND, OR, NOT

● =, >, >=, <, <=, <>

 

 

□ LIKE

● 문법

◎ 문자 표현식 [NOT] LIKE 패턴 값 [ESCAPE 이스케이프문자]

◎ 문자 표현식과 패턴 값을 비교한다.

◎ %(퍼센트) : 아무 값들이 입력되어도 된다(값이 없어도 됨).

◎ _(언더라인) : 한 글자는 아무 값이 입력되어도 되지만 값이 있어야 한다.

 

[LIKE 사용 예시]

//중간에 원이라는 단어가 들어간 회원(좋은회원, 회원, 원)
select m from Member m
where m.username like '%원%'

//처음에 회원이라는 단어가 포함(회원, 회원1, 회원ABC)
where m.username like '회원%'

//회원A, 회원1
where m.username like '회원_'

//회원 %
where m.username like '회원\%' ESCAPE '\'

 

 

□ IS NULL

● 문법

◎ {단일 값 경로 | 입력 파라미터} IS [NOT] NULL

◎ NULL 인지 비교한다.

◎ NULL은 = 으로 비교하면 안 되고 반드시 IS NULL을 사용해야 한다.

 

[IS NULL 사용 예시]

where m.username is null

 

 

 

■ CASE 식

특정 조건에 따라 분기할 때 CASE 식을 사용한다.

CASE 식은 크게 4가지로 분류된다.

 

[기본 CASE 식]

select
	case when m.age <= 10 then '학생요금'
	     when m.age >= 60 then '경로요금'
	     else '일반요금'
	end
from Member m

기본 CASE식은 when에 조건식이 들어간다.

 

[심플 CASE 식]

select
	case t.name
		when '팀A' then '인센티브110%'
		when '팀B' then '인센티브120%'
	     else '인센티브105%'
	end
from Team t

심플 CASE는 조건식을 사용할 수 없다. 

대신 조건 대상(t.name)을 지정해주어야 한다.

 

[COALESCE]

select coalesce(m.username, '이름 없는 회원') from Member m

COALESCE는 대상을 차례대로 조회해서 null이 아니면 반환한다.

위 예제에서는 m.username이 null이면 '이름 없는 회원'을 반환한다.

 

[NULLIF]

select NULLIF(m.username, '관리자') from Member m

두 값이 같으면 null을 반환하고 다르면 첫 번째 값을 반환한다.

위 예제는 m.username이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환한다.

 

 

■ 벌크 연산

엔티티를 수정하려면 영속성 컨텍스트의 변경 감지 기능이나 병합을 사용하고, 삭제하려면 EntityManager.remove() 메서드를 사용한다.

하지만 이런 방법으로 수백 개 이상의 엔티티를 하나씩 처리하기에는 시간이 너무 오래 걸린다.

이럴 때 여러 건을 한 번에 수정하거나 삭제하는 벌크 연산을 사용하면 된다.

 

[벌크 연산 예시]

String qlString = 
	"update Product p " +
	"set p.price = p.price * 1.1 " +
	"where p.stockAmount < :stockAmount";
    
int resultCount = em.createQuery(qlString)
				.setParameter("stockAmount", 10)
				.excuteUpdate();

위 JPQL을 실행하면, 재고가 10개 미만인 모든 상품의 가격을 10% 상승시킨다.

 

벌크 연산은 executeUpdate() 메서드를 이용한다.

이 메서드는 벌크 연산으로 영향을 받은 엔티티 건수를 반환한다.

 

삭제도 executeUpdate()를 사용해서 처리할 수 있다.

 

 

□  벌크 연산 주의

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날린다.

따라서 벌크 연산 이후에 1차 캐시에 값이 있는 데이터를 조회할 경우 데이터베이스와 값이 다를 수 있다.

 

이런 문제를 해결 방법으로는 벌크 연산을 가장 먼저 실행하거나 벌크 연산 수행 후 영속성 컨텍스트를 초기화하는 방법이 있다.

 

 

 

 

2. JPQL 조인

JPQL도 조인을 지원하는데 SQL 조인과 기능은 같고 문법만 약간 다르다.

 

■ 내부 조인

내부 조인은 INNER JOIN을 사용한다.

INNER는 생략할 수 있다.

 

[내부 조인 사용 예]

//Member와 Team을 조인하는 SQL
em.createQuery("select m from Member m join m.team t")
		.getResultList(); 
        

//실제 SQL
SELECT
	M.ID AS ID,
	M.AGE AS AGE,
	M.TEAM_ID AS TEAM_ID,
	M.NAME AS NAME
FROM
	MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID

JPQL 조인의 가장 큰 특징은 연관 필드를 사용한다는 것이다.

위 예제에서는 m.team이 연관 필드인데 연관 필드는 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 말한다.

※ JPQL 조인은 SQL 조인처럼 사용하면 문법 오류가 발생한다.

ex) select m from Member m join Team t

 

 

 

■ 외부 조인

외부 조인은 LEFT OUTER JOIN을 사용하며 OUTER는 생략 가능한다.

 

[외부 조인 예시]

select m from Member left join m.team t

Member와 Team을 외부 조인하였다.

만약 위와 같은 JPQL로 Team을 조회하면, Team이 Null일 경우에도 조인된 Member 수만큼 Team 객체가 채워진다.

다만 이 경우 Null로 조회된 Team 객체의 모든 멤버 값은 null이 된다. 

 

 

 

■ JOIN ON 절

ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있다.

내부 조인의 ON 절은 WHERE 절을 사용할 때와 결과가 같으므로 보통 ON 절은 외부 조인에서만 사용한다.

 

[조인 대상 필터링]

select m, t from Member m left join m.team t on t.name = 'A'

Member와 Team을 조인하면서, 팀 이름이 A인 팀만 조인한다.

 

[연관관계없는 엔티티 외부 조인]

select m, t from Member m left join Team t on m.username = t.name

만약 Member와 Team이 연관관계가 없다면 ON 절을 활용해서 조인을 할 수 있다.

연관관계가 없기 때문에 join 에 연관 필드를 사용하지 않고 엔티티 이름(join Team t)을 사용한다.

 

 

 

■ 페치 조인(fetch join)

페치 조인은 SQL에 있는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능이다.

페치 조인은 연관된 엔티티나 컬렉션은 한 번에 같이 조회하는 기능으로 join fetch 명령어로 사용할 수 있다.

 

[페치 조인 문법]

페치 조인 :: = [LEFT [OUTER] | INNER] JOIN FETCH 조인경로

 

[엔티티 페치 조인]

//JPQL 실행
selcet m from Member m join fetch m.team


//실제 SQL
SELECT
	M.*, T.*
FROM MEMBER T
INNER JOIN TEAM T ON M.TEAM_ID=T.ID

위와 같이 JPQL을 사용하면 Member와 Team을 조인하면서 Member와 Team 전부를 같이 조회한다.

일반적인 JPQL 조인과는 다르게 페치 조인은 별칭을 사용할 수 없다(join fetch m.team).

※ 단, 하이버네이트는 페치 조인에도 별칭을 허용한다.

 

Member와 Team을 지연 로딩으로 설정했다고 하자.

Member를 조회할 때 페치 조인을 사용하면 팀도 함께 조회되므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티이다.

따라서 연관된 팀을 사용해도 지연 로딩이 일어나지 않고 추가 조회 쿼리가 나가지 않는다.

 

 

□ 컬렉션 페치 조인

일대다 관계인 컬렉션도 페치 조인을 사용할 수 있다.

 

[컬렉션 페치 조인 예시]

//JPQL 사용
select t from Team t join fetch t.members where t.name - '팀A'


//실제 SQL
SELECT
	T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

컬렉션 페치 조인도 Team만 선택했지만 조인한 Member도 함께 조회되는 것을 확인할 수 있다.

 

[컬렉션 페치 조인 결과]

앞의 컬렉션 페치 조인 예시와 같이 조인을 하면 위 예시처럼 상황에 따라 조회 결과가 증가할 수 있다.

※ 일대일, 다대일 조인과 달리, 일대다 조인은 결과가 증가할 수 있다.

 

 

□ 페치 조인과 DISTINCT

SQL의 DISTINCT는 중복된 결과를 제거하는 명령어다.

JPQL의 DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복을 제거한다.

 

[DISTINCT 사용 예]

select distinct t from Team t join fetch t.members where t.name = '팀A'

 

위 예제처럼 distinct를 추가하면 SQL에 DISTICNT를 추가한다.

앞의 컬렉션 페치 조인 결과와 같이 조인한 대상에 의해서 조회 결과가 증가된 상태라면, 데이터가 다르므로 SQL 결과에서는 중복제거가 일어나지 않는다.

 

하지만 어플리케이션에서 distinct 명령어를 보고 중복된 데이터를 걸러낸다.

 

[DISTINCT 적용 결과]

결과적으로 Team은 중복이 제거된 결과가 반환된다.

중복만 제거가 되고, Team 과 연관된 Member들은 전부 조회된 상태이므로 Team을 통해 객체 그래프로 자유롭게 Member를 탐색할 수 있다(team.members).

 

 

□ 페치 조인과 일반 조인의 차이

● JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.

● JPQL은 SELECT 절에 지정한 엔티티만 조회한다.

 

● 패치 조인을 사용하면 연관된 엔티티도 함께 조회한다(즉시 로딩).

※ 페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념

 

 

□ 페치 조인의 특징과 한계

● 연관된 엔티티들을 SQL 한 번으로 조회

◎ 사용에 따라 성능 최적화에 사용된다.

 

● 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선시 된다.

ex) @OneToMany(fetch = FetchType.LAZY)

 

 

● 페치 조인 대상에는 별칭을 줄 수 없다.

◎ 하이버네이트는 가능하지만 사용이 권장되지 않는다.

 

● 둘 이상의 컬렉션은 페치 조인할 수 없다.

◎ 컬렉션*컬렉션의 카테시안 곱이 만들어지므로 주의해야 한다.

◎ 하이버네이트를 사용하면 예외가 발생한다.

 

● 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.

◎ 컬렉션이 아닌 단일 값 연관 필드(일대일, 다대일)들은 패치 조인을 사용해도 페이징 API를 사용할 수 있다.

 

 

최적화가 필요한 곳은 페치 조인을 적용한다.

특히, 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.

 

하지만 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적일 수 있다.

 

 

 

 

3. 엔티티 활용

■ 경로 표현식

경로 표현식이란 .(점)을 찍어 객체 그래프를 탐색하는 것이다.

 

경로 표현식의 종류는 다음과 같이 분류된다.

 

● 상태 필드 

◎ 단순히 값을 저장하기 위한 필드(필드 or 프로퍼티)

ex) m.username, m.age

 

● 연관 필드

◎ 연관관계를 위한 필드, 임베디드 타입 포함(필드 or 프로퍼티)

◎ 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티

ex) m.team

◎ 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션

ex) m.orders(컬렉션)

 

 

각 경로 표현식은 다음과 같은 특징이 있다.

 

● 상태 필드 경로

◎ 경로 탐색의 끝. 더는 탐색할 수 없다.

 

● 단일 값 연관 경로

◎ 묵시적으로 내부 조인이 일어난다.

◎ 단일 값 연관 경로는 계속해서 탐색할 수 있다.

ex) m.team.name

 

● 컬렉션 값 연관 경로

◎ 묵시적으로 내부 조인이 일어난다.

◎ 더는 탐색할 수 없다.

◎ 단, FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있다.

 

[단일 값 연관 필드 예시]

//실행 JPQL
select o.member from Order o


//실제 실행되는 JPQL
select m.* from Orders o inner join Member m on o.member_id=m.id

실행한 JPQL을 보면 o.member를 통해 Order 에서 Member로 단일 값 연관 필드 경로 탐색을 했다.

이 경우, 실제 실행되는 JPQL에서는 내부 조인이 일어나는데 이것을 묵시적 조인이라 한다.

※ 묵시적 조인은 모두 내부 조인이다.

 

[컬렉션 값 연관 필드 예시]

select t.members from Team //탐색 가능

select t.members.username from Team t //탐색 실패

select m.username from Team t join t.members m //탐색 가능

JPQL은 컬렉션에서 경로 탐색을 시작하는 것을 허락하지 않는다.

만약 컬렉션에서 경로 탐색을 하고 싶으면 위 예시의 마지막 코드처럼 조인을 사용해서 새로운 별칭을 획득해야 한다.

 

 

□ 경로 탐색을 사용한 묵시적 조인 시 주의사항

● 묵시적 조인은 항상 내부 조인이다

● 컬렉션은 경로 탐색의 끝이다. 컬렉션에서 경로 탐색을 하려면 명시적 조인으로 별칭을 얻어야 한다.

● 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 준다.

 

조인이 성능상 차지하는 부분은 아주 크다.

묵시적 조인은 조인이 일어나는 상황을 정확히 파악하기 어렵다는 단점이 있다.

특히 조인은 SQL 튜닝에 중요한 부분이기 때문에 가급적 묵시적 조인 대신에 명시적 조인을 사용하는 것이 권장된다.

 

 

 

■ 다형성 쿼리

[Item 상속 관계]

Item이 위와 같은 상속관계를 갖고 있다고 하자.

 

JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회한다(단일 테이블 전략, 조인 전략 상관없음).

 

 

□ TYPE

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')

type()을 사용하면 구분 칼럼을 사용해서 특정 자식 타입만 조회할 수 있다.

 

 

□ TREAT

TREAT는 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.

JPA 표준은 FROM, WHERE 절에서 사용할 수 있지만, 하이버네이트는 SELECT 절에서도 TREAT를 사용할 수 있다.

 

[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'

treat를 사용해서 부모 타입인 Item을 자식 타입인 Book으로 다룬다.

따라서 Book의 author 필드에 접근할 수 있다.

 

 

 

■ 엔티티 직접 사용

JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.

 

[엔티티 직접 사용 예시]

//JPQL
select count(m.id) from Member m //엔티티 아이디 사용
select count(m) from Member m //엔티티 직접 사용


//SQL
select count(m.id) as cnt from Member m //둘다 같은 SQL이 실행됨

JPQL에서 엔티티 아이디를 사용하나 엔티티를 직접 사용하나 결국에 엔티티의 기본 키값으로 SQL이 나간다.

 

[엔티티 직접 사용 - 파라미터]

//JPQL
em.createQuery("select m from Member m where m = :member")
	.setParameter("member", member)
	.getResultList(); //엔티티 직접 사용
    
em.createQuery("select m from Member m where m.id = :memberId")
	.setParameter("memberId", memberId)
	.getResultList(); //엔티티의 아이디 사용
    
    
//SQL
selecet m.* from Member m where m.id=? //같은 SQL이 실행된다.

엔티티를 직접 파라미터로 사용할 때도 결국 SQL에서는 엔티티의 기본 키값으로 변환된다.

 

 

경로 탐색으로 표현된 엔티티는 외래 키 값이 사용된다.

ex) m.team의 경우 외래 키로 변환돼서 SQL이 나간다.

'Spring > JPA' 카테고리의 다른 글

#9 값 타입  (0) 2021.08.30
#8 프록시와 연관관계 관리  (0) 2021.08.29
#7 고급 매핑  (0) 2021.08.28
#6 다양한 연관관계 매핑  (0) 2021.08.28
#5 연관관계 매핑 기본  (0) 2021.08.27

댓글