경로 표현식
- JPQL에서 .(점)을 찍어 객체 그래프를 탐색하는 것
select m.username // 경로 표현식 사용
from Member m
join m.team t // 경로 표현식 사용
join m.orders o // 경로 표현식 사용
where t.name '팀A' // 경로 표현식 사용
- 경로 표현식의 용어 정리
- 상태 필드 : 단순히 값을 저장하기 위한 필드 (필드 or 프로퍼티)
- 연관 필드 : 객체 사이의 연관관계를 위한 필드, 임베디드 타입 포함 (필드 or 프로퍼티)
- 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
- 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
// 상태 필드, 연관 필드
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name = "name")
private String username; // 상태 필드 t.username
private Integer age; // 상태 필드 t.age
@ManyToOne(..)
private Team team; // 연관 필드 (단일 값 연관 필드) m.team
@OneToMany(..)
private List<Order> orders; // 연관 필드 (컬렉션 값 연관 필드) m.orders
- 경로 표현식과 특징
JPQL에서 경로 표현식을 사용해서 경로 탐색을 하려면 3가지 경로에 따라 어떤 특징이 있는지 이해가 필요
- 상태 필드 경로 : 경로 탐색의 끝으로 더는 탐색할 수 없음
- 단일 값 연관 경로 : 묵시적으로 내부 조인이 일어나며 계속 탐색할 수 있음
- 컬렉션 값 연관 경로 : 묵시적으로 내부 조인이 일어나며 FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로만 탐색 가능
// 상태 필드 경로 탐색
// JPQL의 m.username, m.age는 상태 필드 경로 탐색
select m.username, m.age form Member m
// JPQL을 실행한 결과 SQL
select m.name, m.age
from Member m
// 단일 값 연관 경로 탐색
// o.member를 통해 주문에서 회원으로 단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부 조인 발생
// 묵시적 조인 (묵시적 조인은 모두 내부 조인이며 외부 조인으로 명시적으로 JOIN 키워드 사용 필수)
select o.member from Order o
// JPQL을 실행한 결과 SQL
select m.*
from Orders o
inner join Member m on o.member_id=m.id
// 복잡한 단일 값 연관 경로 탐색
// 주문 중에서 상품명이 'productA'고 배송지가 'JINJU'인 회원이 소속된 팀을 조회
select o.member.team
from Order o
where o.product.name = 'productA' and o.address.city = 'JINJU'
// 실행된 SQL
// 총 3번의 조인이 발생
// o.address처럼 임베디드 타입에 접근도 단일 값 경로 탐색이지만 주문 테이블에 이미 포함되어 있어 조인 발생 X
select t.*
from Orders o
inner join Member m on o.member_id=m.id
inner join Team t on m.team_id=t.id
inner join Product t on o.product_id=p.id
where p.name='productA' and o.city='JINJU'
// 컬렉션 값 연관 경로 탐색
// 컬렉션 값에서 경로 탐색을 시도할 경우 실패
select t.members from Team t // 성공 (컬렉션까지는 경로 탐색 가능)
select t.members.username from Team t // 실패 (컬렉션에서 경로 탐색을 시작하는 것은 불가능)
// 만약 컬렉션에서 경로 탐색을 하고 싶다면 조인을 사용해서 새로운 별칭을 획득해야 함
// 아래의 경우 join t.member m으로 컬렉션에 새로운 별칭을 얻었으므로
// 별칭 m부터 다시 경로 탐색을 할 수 있음
select m.username from Team t join t.members m
+) 컬렉션은 컬렉션의 크기를 구할 수 있는 size라는 기능 사용 가능
// size를 사용하면 COUNT 함수를 사용하는 SQL로 적절히 변환됨
select t.members.size from Team t
- 경로 탐색을 사용한 묵시적 조인 시 주의사항
- 항상 내부 조인
- 컬렉션은 경로 탐색의 끝이므로 컬렉션에서 경로 탐색을 하려면 명시적으로 조인해서 별칭을 얻어야 함
- 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM 절에 영향을 줌
- 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는 단점이 있으므로
성능이 중요하면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자
서브 쿼리
- JPQL은 SQL처럼 서브 쿼리를 지원하는데
서브 쿼리를 WHERE, HAVING 절에서만 사용할 수 있고 SELECT, FROM절에는 사용할 수 없다는 제약이 존재
// 예1) 나이가 평균보다 많은 회원 찾기
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
// 예2) 한 건이라도 주문한 고객 찾기
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
// 예2를 서브 쿼리 대신 컬렉션 값 연관 필드의 size 기능 사용
select m from Member m
where m.orders.size > 0
- 서브 쿼리 함수
EXISTS, {ALL | ANY | SOME}, IN 함수 존재
// EXISTS
// 문법 : [NOT] EXISTS (subquery)
// 서브쿼리에 결과가 존재하면 참, NOT은 반대
// 예) 팀A 소속인 회원
select m from Member m
where exists (select t from m.team t where t.name = '팀A')
// {ALL | ANY | SOME}
// 문법 : {ALL | ANY | SOME} (subquery)
/* 설명 : 비교 연산자 { = | > | >= | < | <= | <> } 와 같이 사용하며
ALL이면 조건을 모두 만족하면 참
ANY 혹은 SOME은 같은 의미이며 조건을 하나라도 만족하면 참 */
// 예1) 전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from Product p)
// 예2) 어떤 팀이든 팀에 소속된 회원
select m from Member m
where m.team = ANY (select t from Team t)
// IN
// 문법 : [NOT] IN (subquery)
// 설명 : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
// 참고로 IN은 서브 쿼리가 아닌 곳에서도 사용
// 예) 20세 이상을 보유한 팀
select t from Team t
where t IN (select t2 From Team t2 JOIN t2.members m2 where m2.age >= 20)
조건식
- 타입 표현 : 대소문자는 구분하지 않음
- 문자
작은 따옴표 사이에 표현하며 작은 따옴표를 표현하고 싶다면 작은 따옴표 연속 두 개 사용
예) 'HELLO', 'She''s'
- 숫자
L(Long 타입 지정), D(Double 타입 지정), F(Float 타입 지정)
예) 10L, 10D, 10F
- 날짜
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
- 연산자 우선 순위
- 경로 탐색 연산
(.)
- 수학 연산
+, -(단항 연산자), *, /, +, -
- 비교 연산
=, >, >=, <, <=, <>(다름),
[NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF], [NOT] EXISTS
- 논리 연산
NOT, AND, OR
- 논리 연산과 비교식
- 논리 연산
AND : 둘 다 만족하면 참
OR : 둘 중 하나라도 만족해도 참
NOT : 조건식의 결과 반대
- 비교식
= | > | >= | < | <= | <>
- Between, IN, Like, NULL 비교
- Between 식
- IN 식
- Like 식
- NULL 비교식
// Between 식
// 문법 : X [NOT] BETWEEN A AND B
// 설명 : X는 A ~ B 사이의 값이면 참 (A, B 값 포함)
// 예) 나이가 10 ~ 20인 회원
select m from Member m
where m.age between 10 and 20
// IN 식
// 문법 : X [NOT] IN (예제)
// X와 같은 값이 예제에 하나라도 있으면 참
// 예) 이름이 회원1이나 회원2인 회원 찾기
select m from Member m
where m.username in ('회원1', '회원2')
// Like 식
// 문법 : 문자표현식 [NOT] LIKE 패턴값 [ESCAPE 이스케이프문자]
// 설명 : 문자표현식과 패턴값을 비교
// % : 아무 값들이 입력되어도 되며 값이 없어도 됨
// _ : 한 글자는 아무 값이 입력되어도 되지만 값이 있어야 함
// 예1) 중간에 원이라는 단어가 들어간 회원 (예 : 좋은회원, 회원, 원)
select m from Member m
where m.username like '%원%'
// 예2) 처음에 회원이라는 단어가 포함된 회원 (예 : 회원1, 회원ABC)
select m from Member m
where m.username like '회원%'
// 예3) 마지막에 회원이라는 단어가 포함된 회원 (예 : 좋은 회원, A회원)
select m from Member m
where m.username like '%회원'
// 예4) 회원A, 회원1
select m from Member m
where m.username like '회원_'
// 예5) 회원3
select m from Member m
where m.username like '__3'
// 예6) 회원%
select m from Member m
where m.username like '회원\%' ESCAPE '\'
// NULL 비교식
// 문법 : {단일값 경로 | 입력 파라미터} IS [NOT] NULL
// 설명 : NULL인지 비교하며 NULL은 =으로 비교하면 안 되고 꼭 IS NULL을 사용해야 함
// 예)
where m.username is null
where null = null // 거짓
where 1 = 1 // 참
- 컬렉션 식
컬렉션에만 사용하는 특별한 기능으로 컬렉션은 컬렉션 식 이외에 다른 식은 사용할 수 없음
예) where m.orders is null (오류 발생)
// 빈 컬렉션 비교 식
// 문법 : {컬렉션 값 연관 경로} IS [NOT] EMPTY
// 설명 : 컬렉션에 값이 비었으면 참
// 예) 주문이 하나라도 있는 회원 조회
select m from Member m
where m.orders is not empty
// 실행된 SQL
select m.* from Member m
where
exits (
select o.id
from Orders o
where m.id=o.member_id
)
// 컬렉션의 멤버 식
// 문법 : {엔티티나 값} [NOT] MEMBER [OF] {컬렉션 값 연관 경로}
// 설명 : 엔티티나 값이 컬렉션에 포함되어 있으면 참
// 예)
select t from Team t
where :memberParam member of t.members
- 스칼라 식
스칼라는 숫자, 문자, 날짜, case, 엔티티 타입(엔티티의 타입 정보) 같은 가장 기본적인 타입을 말함
- 수학식
- 단항 연산자 : +, -
- 사칙연산 : *, /, +, -
- 문자함수
- CONCAT(문자, 문자2, ˙˙˙)
문자를 합침
예) CONCAT('A', 'B') = AB
- SUBSTRING(문자, 위치, [길이])
위치부터 시작해 길이만큼 문자를 구하며 길이 값이 없으면 나머지 전체 길이를 뜻함
예) SUBSTRING('ABCDEF', 2, 3) = BCD
- TRIM([[LEADING | TRAILING | BOTH] [트림문자] FROM] 문자)
LEADING : 왼쪽, TRAILING : 오른쪽, BOTH : 양쪽 다 트림 문자를 제거. 기본값은 BOTH, 트림 문자 기본값은 공백
예) TRIM(' ABC ') = 'ABC'
- LOWER(문자)
소문자로 변경
예) LOWER('ABC') = 'abc'
- UPPER(문자)
대문자로 변경
예) UPPER('abc') = 'ABC'
- LENGTH(문자)
문자 길이
예) LENGTH('ABC') = 3
- LOCATE(찾을 문자, 원본 문자, [검색시작위치])
검색위치부터 문자를 검색하며 1부터 시작하며 못 찾으면 0 반환
예) LOCATE('DE', 'ABCDEFG') = 4
- 수학함수
- ABS(수학식)
절대값을 구함
예) ABS(-10) = 10
- SQRT(수학식)
제곱근을 구함
예) SQRT(4) = 2.0
- MOD(수학식, 나눌 수)
나머지를 구함
예) MOD(4, 3) = 1
- SIZE(컬렉션 값 연관 경로식)
컬렉션의 크기를 구함
예) SIZE(t.members)
- INDEX(별칭)
LIST 타입 컬렉션의 위치 값을 구함. 단 컬렉션이 @OrderColumn을 사용하는 LIST 타입일 때만 사용 가능
예) t.members m where INDEX(m) > 3
- 날짜함수
- CURRENT_DATE : 현재 날짜
- CURRENT_TIME : 현재 시간
- CURRENT_TIMESTAMP : 현재 날짜 시간
+) 데이터베이스는 각자의 방식으로 더 많은 날씨 함수를 지원하며
각각의 날짜 함수는 하이버네이트가 제공하는 데이터베이스 방언에 등록되어 있음 예) 오라클 방언의 to_date 함수
// 예) 현재 날짜, 현재 시간, 현재 날짜 시간 조회
select CURRENT_DATE, CURRENT_TIME, CURRENT)TIMESTAMP from Team t
// 결과 : 2013-08-19, 23:38:17, 2013-08-19 23:38:17.736
// 예) 종료 이벤트 조회
select e from Event e where e.endDate < CURRENT_DATE
// 하이버네이트는 날짜 타입에서 년, 월, 일, 시간, 분, 초 값을 구하는 기능을 제공
select year(CURRENT_TIMESTAMP), month(CURRENT_TIMESTAMP), day(CURRENT_TIMESTAMP)
from Member
- CASE 식
특정 조건에 따라 분기할 때 CASE 식을 사용
- 기본 CASE
- 심플 CASE
- COALESCE
- NULLIF
// 기본 CASE
/* 문법 :
CASE
{WHEN <조건식> THEN <스칼라식>}+
ELSE <스칼라식>
END */
// 예)
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
// 심플 CASE
// 조건식을 사용할 수 없지만, 문법이 단순하며 자바의 switch case문과 비슷
/* 문법:
CASE <조건대상>
{WHEN <스칼라식1> THEN <스칼라식2>}+
ELSE <스칼라식>
END */
// 예)
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브 105%'
end
from Team t
// COALESCE
// 문법 : COALESCE(<스칼라식>, {,<스칼라식>}+)
// 설명 : 스칼라식을 차례대로 조회해서 null이 아니면 반환
// 예) m.username이 null이면 '이름 없는 회원'을 반환
select coalesce(m.username, '이름 없는 회원') from Member m
// NULLIF
// 문법 : NULLIF(<스칼라식>, <스칼라식>)
// 설명 : 두 값이 같으면 null을 반환하고 다르면 첫 번째 값을 반환
// 집합 함수는 null을 포함하지 않으므로 보통 집합 함수와 함께 사용
// 예) 사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
다형성 쿼리
- 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 생략
// Item의 자식도 함께 조회
List resultList = em.createQuery("select i from Item i").getResultList();
// 단일 테이블 전략(InheritanceType.SINGE_TABLE)을 사용할 때 실행되는 SQL
SELECT * FROM ITEM
// 조인 전략(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
- TYPE
엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용
// 예) Item 중에서 Book, Movie를 조회
select i from Item i
where type(i) IN (Book, Movie)
// 실행된 SQL
SELECT i FROM Item i
WHERE i.DTYPE in ('B', 'M')
- TREAT
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용하며 FROM, WHERE절에서 사용할 수 있음
// 예) 부모인 Item과 자식 Book
// treat를 사용해서 부모 타입인 Item을 자식 타입인 Book으로 다루어 author 필드에 접근
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'
사용자 정의 함수 호출
// 문법
function_invocaation::= FUNCTION(function_name, {, function_arg}*)
// 예)
select function('group_concat', i.name) from Item i
- 하이버네이트 구현체를 사용하면 방언 클래스를 상속해서 구현하고 사용할 데이터베이스 함수를 미리 등록해야 함
// 방언 클래스 상속
public class MyH2Dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
// hibernate.diaclect에 해당 방언을 등록
<property name="hibernate.dialect" value="hello.MyH2Dialect" />
// 하이버네이트 구현체를 사용하면 축약해서 사용 가능
select group_concat(i.name) from Item i
기타 정리
- enum
= 비교 연산만 지원
- 임베디드 타입
비교를 지원하지 않음
- EMPTY STRING
JPA 표준은 ''을 길이 0인 Empty String으로 정했지만 데이터베이스에 따라 ''를 NULL로 사용하는 데이터베이스도 있음
- NULL 정의
- 조건을 만족하는 데이터가 하나도 없으면 NULL
- NULL은 알 수 없는 값이며 NULL과의 모든 수학적 계산 결과는 NULL이 됨
- NULL == NULL은 알 수 없는 값
- NULL us NULL은 참
- NULL(U)값과 TRUE(T), FALSE(F)의 논리연산
T |
F |
U |
AND |
OR |
NOT
|
T |
F |
U |
T |
T |
T |
F |
F |
F |
F |
F |
F |
F |
T |
U |
F |
U |
U |
U |
U |
U |
엔티티 직접 사용
- 기본 키 값
객체 인스턴스는 참조 값으로 식별하고 테이블 로우는 기본 키 값으로 식별하므로
JPQL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 기본 키 값을 사용
// 엔티티 객체를 직접 사용하는 JPQL
select count(m.id) from Member m // 엔티티의 아이디를 사용
select count(m) from Member m // 엔티티를 직접 사용 (엔티티의 별칭을 직접 넘겨줌)
// 실행된 SQL
// 엔티티를 직접 사용하면 JPQL이 SQL로 변한될 때 해당 엔티티의 기본 키를 사용함
// 그러므로 JPQL의 count(m)이 SQL에서 count(m.id)로 변환된 것을 확인 가능
select count(m.id) as cnt
from Member m
// 엔티티를 파라미터로 직접 받는 코드
String qlString = "select m from Member m where m = :member";
List resultList = em.createQuery(qlString)
.setParameter("member", member)
.getResultList();
// 실행된 SQL
// JPQL에서 where m = :member로 엔티티를 직접 사용하는 부분이
// SQL에서 where m.id=?로 기본 키 값을 사용하도록 변환됨
select m.*
from Member m
where m.id=?
// 식별자 값을 직접 사용하는 코드 (SQL은 위와 결과는 같음)
// 엔티티 대신 식별자 값을 직접 사용
String qlString = "select m from Member m where m.id = :memberId";
List resultList = em.createQuery(qlString)
.setParameter("memberId", 4L)
.getResultList();
- 외래 키 값
JPQL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 외래 키 값을 사용
// 외래 키 대신 엔티티를 직접 사용하는 코드
// 특정 팀에 소속된 회원을 찾음
Team team = em.find(Team.class, 1L);
// 기본 키 값이 1L인 팀 엔티티를 파라미터로 사용
String qlString = "select m from Member m where m.team = :team";
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();
// 실행된 SQL
// m.team은 현재 team_id라는 외래 키와 매핑되어 있음
select m.*
from Member m
where m.team_id=?(팀 파라미터 ID 값)
// 외래 키에 식별자를 직접 사용하는 코드 (SQL은 위와 결과는 같음)
// 엔티티 대신 식별자 값을 직접 사용
// m.team.id의 경우 Member 테이블이 team_id 외래 키를 가지고 있으므로 묵시적 조인이 일어나지 않음
// 만약 m.team.name을 호출하면 Member와 Team 간에 묵시적 조인이 발생
String qlString = "select m from Member m where m.team.id = :teamId";
List resultList = em.createQuery(qlString)
.setParameter("teamId", 1L)
.getResultList();
Named 쿼리 : 정적 쿼리
- JPQL 쿼리는 크게 동적 쿼리와 정적 쿼리로 나누어짐
- 동적 쿼리 : em.createQuery("select ..") 처럼 JPQL을 문자로 완성해서 직접 넘기는 것
- 정적 쿼리 : 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용하며 Named 쿼리라고 하며 한 번 정의하면 변경할 수 없음
- Named 쿼리는 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법이며
애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해두므로
오류를 빨리 확인할 수 있고 사용하는 시점에 파싱된 결과를 재사용하므로 성능상 이점도 존재
또한 변하지 않는 정적 SQL이 생성되므로 데이터베이스 조회 성능 최적화에도 도움이 됨
Named 쿼리는 @NamedQuery 어노테이션을 사용해서 자바 코드를 작성하거나 XML 문서에 작성할 수 있음
- Named 쿼리를 어노테이션에 정의
// @NamedQuery 어노테이션으로 Named 쿼리 정의
// @NamedQuery.name에 쿼리 이름을 부여하고 @NamedQuery.query에 사용할 쿼리를 입력
@Entity
@NamedQuery(
name = "Member.findByUsername",
query= "select m from Member m where m.username = :username")
public class Member {
...
}
// @NamedQuery 사용
// em.createNamedQuery() 메소드에 Named 쿼리 이름을 입력하면 됨
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
// 하나의 엔티티에 2개 이상의 Named 쿼리를 정의하려면 @NamedQueries 어노테이션 사용
@Entity
@NamedQuerise({
@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 {
...
}
// @NamedQuery 어노테이션
@Target({TYPE})
public @interface NamedQuery {
String name(); // Named 쿼리 이름 (필수)
String query(); // JPQL 정의 (필수)
LockModeType lockMode() default NONE; // 쿼리 실행 시 락모드를 설정 (쿼리 실행 시 락을 검)
// 2차 캐시를 다룰 때 사용하는 hints
QueryHint() hints() default {}; // JPA 구현체에 쿼리 힌트를 줌 (JPA 구현체에게 제공하는 캐시)
- Named 쿼리를 XML에 정의
어노테이션을 사용하는 것이 직관적이고 편리하지만 Named 쿼리를 작성할 때는 XML을 사용하는 것이 더 편리
또한 자바 언어로 멀티라인 문자를 다루는 것은 상당히 귀찮은 일이므로 XML을 사용
// 멀티라인 사용
"select " +
"case t.name when '팀A' then '인센티브110%' " +
" when '팀B; then '인센티브120%'" +
" else '인센티브105%' end " +
"from Team t";
// 멀티라인을 지원하는 언어 그루비 사용
'''
select
case t.name when '팀A' then '인센티브110%'
when '팀B; then '인센티브120%'
else '인센티브105%' end
from Team t
'''
// 불편함을 해결하기 위해 XML 사용
// XML에 정의한 Named 쿼리
// METE-INF/ormMember.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/hs/persistence/orm" verstion="2.1">
<name-query name="Member.findByUsername">
<query><CDATA[
select m
from Member m
where m.username = :username
]></query>
</name-query>
<name-query name="Member.count">
<query>select count(m) from Member m</query>
</name-query>
</entity-mappings>
// 정의한 ormMember.xml을 인식하도록 META-INF/psersistence.xml에 코드 추가
<persistenace-unit name="jpabook">
<mapping-file>META-INT/ormMember.xml</mapping-file>
...
- 환경에 따른 설정
만약 XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가짐
예) 같은 이름의 Named 쿼리가 있으면 XML에 정의한 것이 사용됨
따라서 애플리케이션이 운영 환경에 따라 다른 쿼리를 실행해야 한다면 각 환경에 맞춘 XML을 준비해두고 XML만 변경해서 배포