JPA에서는 식별자를 통해 em.find() 메서드로 엔티티를 조회하고, 객체 그래프 탐색을 통해서 연관된 객체를 조회한다. 그러나 특정 조건을 걸어서 조회할 때는 모든 엔티티를 조회하고 검색하는 것은 현실성이 없다. 실제 데이터는 객체가 아닌 데이터베이스에 있기 때문에 이런 문제가 발생한다. ORM을 사용하면 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 아닌 엔티티 객체를 대상으로 하는 방법이 필요하다. 그래서 객체를 중심으로 데이터를 다루는 언어가 필요한데, 이를 객체지향 쿼리라고 한다.
객체지향 쿼리 중 하나인 JPQL을 알아보고 JPA에서 공식적으로 지원하는 JPQL을 편하게 사용하도록 도와주는 Crieteria 쿼리, 네이티브 SQL을 먼저 간단하게 알아보자.
//쿼리 생성
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList = 
	em.createQuery(jpql, Member.class).getResultList();
회원 이름이 'kim'인 엔티티를 조회한다. 생성한 jpql에서 m.username은 테이블의 컬럼명이 아닌 엔티티 객체의 필드명이다. em.createQuery() 메서드에 실행할 JPQL과 반환할 엔티티의 클래스 타입(Member.class)를 넘겨주고 getResultList() 메서드를 실행하면 JPA는 JPQL을 SQL로 변환해서 데이터베이스를 조회하고 조회한 결과를 Member 엔티티로 생성해서 반환한다.
이 때, Member 클래스가 id, age, name을 필드로 가지고 Team 클래스와 연관관계를 가지고 있다고 하면 다음과 같은 SQL이 생성된다.
SELECT m.id, m.age, m.name, m.team_id
FROM Member m
WHERE m.name = 'kim'
Criteria는 문자가 아닌 코드로 JPQL을 작성할 수 있다.
JPQL은 위에서 봤던 것처럼 문자로 작성하기 때문에 오타가 나도 컴파일 오류가 일어나지 않는다. (예를 들어 "select m Membee m") 다만 해당 쿼리가 실행되는 런타임 시점에 오류가 발생하게 되는데 Criteria는 JPQL을 문자가 아닌 코드로 작성하기 때문에 컴파일 단계에서 오류를 잡을 수 있다. 
//jpql
// "select m from Member as m where m.username = 'kim'"
//Criteria 사용준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//루트 클래스 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
//쿼리 생성
CriteriaQuery<Member> cq = 
	query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
자바가 제공하는 어노테이션 프로세서(Annotation Processor)기능을 사용하면 어노테이션을 분석해서 클래스를 생성할 수 있다. JPA는 이 기능을 사용해서 Member엔티티 클래스로부터 Member라는 Criteria 전용 클래스를 생성하는데 이를 메티 모델이라고 한다. 메타 모델을 이용하면 "username"부분도 코드로 작성할 수 있다.
m.get("username") -> m.get(Member.username)
JPA에서 공식 지원하지 않지만 JPQL을 편하게 작성하도록 도와주는 빌더 역할을 하는 오픈소스 프로젝트이다.
코드 기반이면서 단순하고 사용하기 쉽다.
//준비
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
//쿼리, 결과조회
List<Member> members = 
	query.from(member)
    .where(member.username.eq("kim"))
    .list(member);
QueryDSL도 어노테이션 프로세서를 이용해서 쿼리 전용 클래스를 만들어야 한다. 그 전용 클래스가 바로 QMember로 Member 엔티티 클래스를 기반으로 생성한 QueryDSL 쿼리 전용 클래스다.
JPA는 SQL을 직접 사용할 수 있게 지원하는데 이것을 네이티브 SQL이라고 한다.
다음과 같은 상황에서 네이티브 SQL을 사용한다.
네이티브 SQL은 특정 데이터베이스에 의존하는 SQL을 작성해야 하는 단점이 있어서 데이테버에시를 변경하면 네이티브 SQL도 변경해야 한다.
String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> resultList = 
	em.createNativeQuery(sql, Member.class).getResultList();
JPA는 JDBC 커넥션을 획득하는 API를 제공하지 않으므로 구현체가 제공하는 방법을 사용해야 한다.
(이 방법은 궁금하면 검색해 보자)
JDBC를 직접 사용하든 마이바티스 같은 SQL 매퍼와 사용하든 모두 JPA를 우회해서 데이터베이스에 접근한다. 이처럼 JPA를 우회하기 때문에 JPA가 전혀 인식을 못하게 되는데 이는 영속성 컨텍스트와 데이터베이스를 불일치 상태로 만들어 데이터 무결성을 훼손할 수 있다.
이런 이슈를 해결하기 위해 JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시해서 데이터베이스와 영속성 컨텍스트를 동기화하면 된다.
스프링 프레임워크는 AOP를 적절히 활용해서 JPA를 우회해서 데이터베이스에 접근하는 메서드를 호출할 때마다 영속성 컨텍스트를 플러시하면 위의 문제를 해결할 수 있다.
JPQL도 SELECT, UPDATE, DELETE 문이 있다. INSERT는 EntityManager.persist() 메서드를 사용하므로 없다.
//JPQL 문법
select_문 :: = 
	select_절
    from_절
    [where_절]
    [groupby_절]
    [having_절]
    [orderby_절]
    
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
JPQL에서 update, delete는 벌크연산인데 뒤에서 자세히 다룬다.
SELECT m FROM Member AS m where m.username = 'Hello'
JPQL을 실행하려면 쿼리 객체를 만들어야 하는데 다음과 같이 두 개의 객체를 사용한다.
TypeQuery<Member> query = 
	em.creqteQuery("SELECT m FROM Member m", Member.class);
    
Query query = 
	em.createQuery("SELECT m.username, m.age from Member m");
예제코드를 보면 반환타입을 Member.class로 지정한 경우 TypeQuery를 사용하였다. 반면에 String 타입인 username과 Integer타입인 age를 반환하는 경우 Query를 사용한디. 이처럼 SELECT 절에서 여러 엔티티나 컬럼을 선택할 때는 반환할 타입이 명확하지 않으므로 Query 객체를 사용해야 한다.
Query 객체는 조회 대상이 둘 이상이면 Object[] 를 반환하고 조회 대상이 하나면 Object를 반환한다.
다음 메서드를 호출하면 실제 쿼리를 실행해서 데이터베이스를 조회한다.
JDBC는 위치 기반 파라미터 바인딩만 지원하지만 JPQL은 이름 기반 파라미터 바인딩도 지원한다.
파라미터를 이름으로 구분하는 바인딩으로 앞에 :를 사용한다.
? 다음에 위치 값을 주면 되며, 위치 값은 1부터 시작한다.
String usernameParam = "User1";
// 이름 기준 파라미터
TypedQuery<Member> query =
	em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
   
List<Member> resultList = query.setParameter("username", usernameParam)
								.getResultList();
                               
// 위치 기준 파라미터
List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
							.setParaemter(1, usernaemParam)
                           .getResultList();
SELECT절에 조회할 대상을 지정하는 것을 프로젝션이라 하며, [SELECT {프로젝션 대상} FROM]으로 대상을 선택한다.
SELECT m FROM Member m			//회원
SELECT m.team FROM Member m 	//팀
회원과 팀을 조회하는데 둘 다 엔티티를 프로젝션 대상으로 사용했다. 이렇게 조회한 엔티티는 영속성 컨텍스트에서 관리된다.
 값 타입의 일종인 임베디드 타입으로도 프로젝션을 사용할 수 있는데, 임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있다.
예를 들어 Order 안에 Address 라는 임베디드 타입을 바로 프로젝션으로 사용할 수 없다는 것인데 다음과 같이 사용해야 올바르다.
String query = "SELECT o.address FROM Order o";
List<Address> addresses = em.creqteQuery(query, Address.class)
								.getResultList();
                               
//실행되는 SQL
select
	order.city,
   order.street,
   order.zipcode
from Orders order
임베디드 타입은 값 타임이므로 이렇게 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입이라 한다.
//예시 중복제거 DISTINCT
SELECT DISTINCT username FROM Member m
// 통계 쿼리
Double orderAmountAvg = em.createQuery("SELECT AVC(o.orderAmount) FROM Order o", Double.classs)
							.getSingleResult();
프로젝션에 여러 값을 선택하면 TypeQuery를 사용할 수 없고 Query 객체를 사용해야 한다. 스칼라 타입뿐만 아니라 엔티티 타입도 여러 값을 함께 조회할 수 있다.
이 때, 한 엔티티는 영속성 컨텍스트에서 관리된다.
여러 필드를 프로젝션으로 조회할 경우 Query를 사용했다. 하지만 UserDTO처럼 의미있는 객체로 변환해서 사용하면 간편하게 사용할 수 있는데 예시를 보자.
public class UserDTO {
	private String username;
    private int age;
    
    //생성자
    public UserDTO(String username, int age) {
    	this.username = uesrname;
        this.age = age;
    }
}
// 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 명령어를 사용하면 반환받을 클래스를 지정할 수 있는데 이 클래스의 생성자에 JQPL 조회 결과를 넘겨줄 수 있다. 또한 NEW 명령어를 사용한 클래스로 TypedQuery 를 사용할 수 있어 지루한 객체 변환 작업을 줄일 수 있다.
NEW 명령어를 사용하면 다음과 같은 주의사항이 있다.
데이터베이스마다 페이징을 처리하는 SQL 문법이 다르다. JPA에서는 페이징을 다음 두 API로 추상화하였다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
query.setFirstResult(10);	// 11번째부터 데이터를 조회한다.
query.setMaxResults(20);	// 11번째부터 20개를 조회한다. (즉, 11~30번 데이터)
query.getResultList();
데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있는 것은 데이터베이스 방언 (Dialect) 덕분이다.
데이터베이스별 페이징 쿼리가 궁금하면 검색해보자.
페이징 SQL을 더 최적화하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야 한다.
select
	COUNT(m),	//회원 수, 결과수를 구한다. 반환타입 Long
    SUM(m.age),	//나이 합, 숫자타입만 사용가능. 반환타입 정수 -> Long, 소수 -> Double,// BigInteger, BigDecimal 합은 해당 타입으로 반환
    AVG(m.age),	//평균나이, 평균값을 구한다. 숫자타입만 사용가능하며 반환타입 Double
    MAX(m.age),	//최대 나이, 최대값을 구한다. 문자, 숫자, 날짜 등에 사용
    MIN(m.age), //최소 나이, 최소값을 구한다. 문자, 숫자, 날짜 등에 사용
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
HAVING AVG(m.age) >= 10
ORDER BY 는 결과를 정렬할 때 사용한다
select m from Member m order by m.age DESC, m.username ASC
JPQL도 SQL조인과 비슷하게 조인을 지원한다. INNER JOIN 사용 시 INNER는 생략해도 된다.
회원과 팀을 내부조인하여 "팀A"라는 팀에 소속된 회원을 조회하는 JPQL을 살펴보자.
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();
//실행 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
WHERE
	T.NAME=?    
JPQL 조인의 가장 큰 특징은 조인할 때 m.team 과 같이 연관 필드(다른 엔티티와 연관관계를 가지기 위해 사용하는 필드)를 사용한다는 것이다.
JPQL의 외부조인은 기능상 SQL의 외부조인과 같으며 LEFT OUTER JOIN을 사용하는데 OUTER는 생략해도 된다.
//외부조인 JPQL
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
WHERE t.name = :teamName
String jpql = "SELECT m FROM Member m LEFT JOIN m.team t WHERE t.name = :teamName";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
query.setParameter("teamName", "TeamA");
List<Member> result = query.getResultList();
//실행 SQL
SELECT
	M.ID AS ID,
    M.AGE AS AGE,
    M.TEAM_ID AS TEAM_ID,
    M.NAME AS NAME
FROM
	MEMBER M LEFT OUTER JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE
	T.NAME ?
일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라 한다.
SELECT t, m FROM Team t LEFT JOIN t.members m
위에 t LEFT JOIN t.members 부분으로 팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인했다.
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
세타 조인(Theta Join)은 관계형 데이터베이스에서 조인 조건을 지정해 두 개 이상의 테이블을 결합하는 방식입니다. 이 조인의 가장 큰 특징은 단순히 등가 조건(=)에만 의존하지 않고, 비교 연산자(=, <, >, <=, >=, <>, etc.)를 사용하여 다양한 조건으로 데이터를 결합할 수 있다는 점입니다.
SQL 문장에서 세타 조인은 일반적으로 WHERE 절을 사용해 구현됩니다.
=, <, >, <=, >=, <> 등Employees 테이블:
EmpID | Name   | DeptID
------|--------|-------
1     | Alice  | 10
2     | Bob    | 20
3     | Charlie| 30
Departments 테이블:
DeptID | DeptName
-------|---------
10     | HR
20     | IT
30     | Finance
SELECT e.Name, d.DeptName
FROM Employees e, Departments d
WHERE e.DeptID >= d.DeptID;
Name     | DeptName
---------|----------
Alice    | HR
Alice    | IT
Alice    | Finance
Bob      | IT
Bob      | Finance
Charlie  | Finance
세타 조인은 특정 연산 조건에 따라 여러 방식으로 확장될 수 있습니다:
1. 등가 조인(Equal Join): 조건이 =인 경우(내부 조인의 기본 형태).
SELECT e.Name, d.DeptName
FROM Employees e
JOIN Departments d
ON e.DeptID = d.DeptID;
= 이외의 연산자를 사용할 때.SELECT e.Name, d.DeptName
FROM Employees e
JOIN Departments d
ON e.DeptID > d.DeptID;JOIN ON)이 더 권장됩니다. 이는 가독성과 유지보수성을 향상시키며, 세타 조인도 이 문법으로 구현 가능합니다.JPA 2.1부터 조인할 때 ON 절을 지원한다. ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있다. 내부 조인의 ON절은 WHERE 절을 사용할 때와 결과가 같으므로 보통 ON절은 외부 조인에서만 사용한다.
//모든 회원을 조회하면서 회원과 연관된 팀을 조회, 이 때 팀이름이 A인 팀만 조회
//JPQL
select m,t from Member m
left join m.team t 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에서 이야기하는 조인의 종류가 아닌 JPQL에서 성능 최적화를 위해 제공하는 기능이다. 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능인데 join fetch 명령어로 사용한다.
또한 페치조인은 연관 필드에 별칭을 허용하지 않는다.(하이버네이트는 허용한다고 한다...)
페치 조인을 사용하여 회원을 조회하는데 연관된 팀도 같이 조회해보자.
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
						.getResultList();
                        
for (Member member : members) {
	//페치 조인으로 회원과 팀을 함께 조회하여 지연 로딩 발생 안 함
    System.out.println("username = " + member.getUsername() + ", " +
    "teamname = " + member.getTeam().name());
}    
//실행 SQL
SELECT
	M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
엔티티 페치 조인 JPQL에서 select m으로 회원 엔티티만 선택했는데 실행 SQL을 보면 회원과 연관된 팀도 같이 조회되는 것을 확인할 수 있다.
회원과 팀을 지연 로딩으로 설정했다고 가정해보자. 회원을 조회할 때 페치 조인을 사용해서 팀도 함께 조회했으므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티다. 따라서 연관된 팀을 사용해도 지연 로딩이 일어나지 않는다. 그리고 프록시가 아닌 실제 엔티티이므로 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회할 수 있다.
일대다 관계 컬렉션을 페치 조인해보자.
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
//실행 SQL
SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=TEAM_ID
WHERE T.NAME = '팀A'

Team 테이블에서 '팀A'는 하나지만 MEMBER 테이블과 조인하면서 결과가 증가해 '팀A'가 2건 조회된다.
따라서 컬렉션 페치 조인 결과 객체에서 teams 결과 예제를 보면 주소가 같은 '팀A'를 2건 가지게 된다.
SQL의 DISTINCT는 중복된 결과를 제거하는 명령어이다. JPQL에서 DISTINCT 명령어는 SQL에 DISTINCET를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복을 제거한다.
select distinct t
from Team t join fetch t.members
where t.name = '팀A'
SQL에 DISTINCT를 추가되어도 각 로우가 다르므로 SQL의 DISTINCT는 효과가 없다. 애플리케이션에서 distinct 명령어를 보고 중복된 데이터를 걸러내어 select distinct t를 통해서 팀 엔티티의 중복을 제거한다.
//일반 조인
select t
from Team t join t.members m
where t.name = '팀A'
//실행된 SQL
SELECT T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
JPQL은 결과를 반환할 때 연관관계까지 고려하지 않는다. 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다. 따라서 팀 엔티티만 조회하고 연관된 컬렉션은 조회하지 않는다.
만약 회원 컬렉션을 지연 로딩으로 설정하면 프록시나 아직 초기화하지 않은 컬렉션 래퍼를 반환한다. 즉시 로딩으로 설정하면 회원 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행한다.
//페치 조인
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'
페치조인을 사용하면 위처럼 연관된 엔티티(Members)의 데이터도 가져온다.
페치 조인은 SQL 한 번으로 연관된 여러 엔티티를 조회할 수 있어서 성능 최적화에 상당히 유용하다. 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이지만 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 다른 방법이 더 효과적이다. 이와 같은 경우 억지로 페치 조인을 사용하기보다 여러 테이블에서 필요한 필드들만 조회해서 DTO로 변환하는 것이 더 효과적이다.
상태필드 state field :단순히 값을  저장하기 위한 필드(필드 or 프로퍼티)
연관필드 association field : 연관관계를 위한 필드, 임베디드 타입 포함(필드 or 프로퍼티)
- 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
- 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
상태 필드는 단순히 값을 저장하는 필드이고 연관 필드는 객체 사이의 연관관계를 맺기 위해 사용하는 필드이다.
@Entity
public class Member {
    
    @Id
    @GeneratedValue
    private Long id;
    
    @Column(name = "name")
    private String username;    //상태 필드
    private Integer age;    //상태 필드
    
    @ManyToOne(...)
    private Team team;  //연관필드(단일 값 연관 필드)
    
    @OneToMany(...)
    private List<Order> orders; //연관필드(컬렉션 값 연관 필드)
}
//jpql
select o.member from Order o
//실행 SQL
select m.*
from Orders o
	inner join Member m on o.member_id=m.id
o.member를 통해 주문에서 회원으로 단일 값 연관 필드로 경로 탐색을 했다. 단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부 조인이 일어난다. 이것을 묵시적 조인이라 한다.
//JPQL
select o.member.team
from Order o
where o.product.name = 'A' and o.address.city = 'JEJU';
//실행 SQL
select t.*
from Order o
inner join Member m on o.member_id=m.id
inner join Team t on m.team_id=t.id
inner join Product p on o.product_id=p.id
where p.name='A' and o.city='JEJU'
주문상품이 'A'이고 배송지가 'JEJU'인 회원이 소속된 팀을 조회하는 내용이다. SQL을 보면 총 3번의 조인이 발생했다. o.address처럼 임베디드 타입에 접근하는 것도 단일 값 연관 경로 탐색이지만 주문 테이블에 이미 포함되어 있으므로 조인이 발생하지 않는다.
컬렉션에서 경로 탐색을 허용하지 않는다. 만약 경로 탐색을 하고 싶다면 조인을 사용해서 새로운 별칭을 획득해야 한다.
select t.members.username from Team t //실패
select m.username from Team t join t.members m	//성공
묵시적 조인이 일어나면 파악하기 어렵다는 단점이 있으므로 명시적 조인을 사용하도록 하자.
JPQL도 SQL처럼 서브 쿼리를 지원한다. 그러나 제약이 존재하는데 서브쿼리를 WHERE, HAVING절에서만 사용가능하고 SELECT, FROM절에서 사용하지 못 한다.
(HQL은 SELECT 절의 서브 쿼리 허용. 일부 JPA 구현체는 FROM 절의 서브 쿼리도 지원한다고 한다.)
한 건이라도 주문한 고객 조회
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
//다른 방법
select m from Member m
where m.order.size > 0
[NOT] EXISTS (subquery)
{ALL | ANY | SOME} (subquery)
[NOT] IN (subquery)


문법 : X [NOT] BETWEEN A AND B
설명 : X는 A ~ B 사이의 값이면 참 (A, B 값 포함)
select m from Memer m
where m.age between 10 and 20
-> 나이가 10~20인 회원 조회
문법 : {단일값 경로 | 입력 파라미터 } IS [NOT] NULL
설명 : NULL인지 비교. NULL 은 = 으로 비교할 수 없다.
where m.username is null
-> where null = null //거짓
컬렉션에만 사용하는 특별한 기능. 컬렉션은 컬렉션 식 이외에 다른 식은 사용 불가능
문법 : {컬렉션 값 연관 경로} IS [NOT] EMPTY
설명 : 컬렉션에 값이 비었으면 참
//JPQL : 주문이 하나라도 있는 회원 조회
select m from Member m
where m.orders is not empty
//실행된 SQL
select m.* from Member m
where
	exists (
    	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, 엔티티 타입(엔티티의 타입 정보)같은 가장 기본적인 타입들을 말한다.


데이터베이스의 현재 시간을 조회한다.
하이버네이트는 날짜 타입에서 년, 월, 일, 시간, 분, 초 값을 구하는 기능을 지원한다.
YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
select year(CURRENT_TIMESTAMP), month(CURRENT_TIMESTAMP), day(CURRENT_TIMESTAMP)
from Member
특정 조건에 따라 분기할 때 CASE식을 사용한다.
CASE
	{WHEN <조건식> THEN <스칼라식> } +
    ELSE <스칼라식>
END
//ex
select
	case when m.age <= 10 then '학생요금'
    	when m.age >= 60 then '경로요금'
        else '일반요금'
    end
from Member m
심플 CASE는 조건식을 사용할 수 없지만 문법이 간단하다. JAVA의 switch case 문과 비슷하다.
CASE <조건대상>
	{WHEN <스칼라식1> THEN <스칼라식2> } +
    ELSE <스칼라식>
END
//ex
select
	case t.name
    	when '팀A' then '인센티브110%'
    	when '팀B' then '인센티브120%'
        else '인센티브105%'
    end
from Team t
//문법 : COALESCE(<스칼라식> {,<스칼라식>}+)
//설명 : 스칼라식을 차례대로 조회해서 null이 아니면 반환한다.
//예 ) m.username이 null이면 '이름 없는 회원' 반환
select coalesce(m.username, '이름 없는 회원') from Member m
//문법 : NULLIF(<스칼라식>, <스칼라식>)
//설명 : 두 값이 같으면 null을 반환하고 다르면 첫 번째 값을 반환한다. 집함 함수는 null을 포함하지 않으므로 보통 집합 함수와 함께 사용한다.
//예 ) 사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
JPQL로 부모 엔티티를 조회하면 자식 엔티티도 함께 조회된다. Item의 자식 엔티티로 Book, Album, Movie가 있다고 하자.
@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.SINGLE_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 조회
//JPQL
select i from Item i
where type(i) IN (Book, Movie)
//SQL
SELECT i FROM Item i
WHERE i.DTYPE in ('B', 'M')
TREAT는 JPA2.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'
JPQL을 보면 treat를 사용해서 부모 타입 Item을 자식 타입 Book으로 다뤄 author 필드에 접근하고 있다.
JPA표준은 ''와 같이 길이 0인 빈 문자열을 Empty String으로 정했지만 데이터베이스에 따라 ''을 NULL로 사용하기도 해서(대표적으로 오라클) 확인하고 사용해야 한다.
| 값 1 | 값 2 | 결과 | 
|---|---|---|
| TRUE | TRUE | TRUE | 
| TRUE | FALSE | FALSE | 
| TRUE | NULL | NULL | 
| FALSE | TRUE | FALSE | 
| FALSE | FALSE | FALSE | 
| FALSE | NULL | FALSE | 
| NULL | TRUE | NULL | 
| NULL | FALSE | FALSE | 
| NULL | NULL | NULL | 
| 값 1 | 값 2 | 결과 | 
|---|---|---|
| TRUE | TRUE | TRUE | 
| TRUE | FALSE | TRUE | 
| TRUE | NULL | TRUE | 
| FALSE | TRUE | TRUE | 
| FALSE | FALSE | FALSE | 
| FALSE | NULL | NULL | 
| NULL | TRUE | TRUE | 
| NULL | FALSE | NULL | 
| NULL | NULL | NULL | 
| 값 | 결과 | 
|---|---|
| TRUE | FALSE | 
| FALSE | TRUE | 
| NULL | NULL | 
객체 인스턴스는 참조 값으로 식별하고 테이블 로우는 기본 키 값으로 식별한다. 따라서 JQPL에서 엔티티 객체를 직접 사용하면 SQL에서는 해당 엔티티의 기본 키값을 사용한다.
select count(m.id) from Member m	// 엔티티의 아이디를 사용
select count(m) from Member m		// 엔티티를 직접 사용
//실행 SQL 동일
select count(m.id) as cnt
from Member m
외래 키 값을 사용하는 예제를 보자. 특정 팀에 소속된 회원을 조회해보자.
Team team = em.find(Team.class, 1L);
String qlString = "select m from Member m where m.team = :team";
List resultList = em.creqteQuery(qlString)
					.setParameter("team", team)
                    .getResultList();
// 실행 SQL
select m.*
from Member m
where m.team_id=? (팀 파라미터의 ID 값)                          
m.team.id를 보면 Member와 Team 간에 묵시적 조인이 일어날 것 같지만 MEMBER 테이블이 team_id 외래 키를 가지고 있으므로 묵시적 조인은 일어나지 않는다.
Named 쿼리는 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해 둔다. 따라서 오류를 빨리 확인할 수있고, 사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이점도 있다. 그리고 Named 쿼리는 변하지 않는 정적 SQL이 생성되므로 데이터베이스의 조회 성능 최적화에 도움이 된다.
@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username")
)
public class Member {
	...
}    
// @NamedQuery 사용
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
							.setParameter("username", "회원1")
                            .getResultList();
어노테이션을 통해 Named 쿼리를 사용하려면 em.createNamedQuery() 메서드에 Named 쿼리 이름을 입력하면 된다.
어노테이션을 사용하는 것이 더 직관적이지만 XML에 정의하는 것이 더 편리하다고 한다.
자바 언어로 멀티라인 문자를 다루는 것이 귀찮기 때문에 그런데 추가적으로 아래와 같은 장점도 있다.
Named 쿼리를 XML에서 정의하는 것이 더 편리할 수 있는 이유는 다음과 같습니다:
//META-INF/ormMember.xml 에 정의한 Named 쿼리
<?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.findByUsernameAndStatus">
    	<query>
        	<![CDATA[
            	SELECT m
            	FROM Member m
            	WHERE m.username = :username
            	AND m.status = :status
        	]]>
    	</query>
	</named-query>
</entity-mappings>
ormMember.xml을 인식하도록 META-INF/persitence.xml에 다음 코드를 추가해야 한다.
<persistence-unit name="jpabook">
  	<mapping-file>META-INF/ormMember.xml</mapping-file>
  	...
만약 XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가진다. 예를 들어 같은 이름의 Named 쿼리가 있으면 XML에 정의한 것이 사용된다. 따라서 애플리케이션이 운영 환경에 따라 다른 쿼리를 실행해야 한다면 각 환경에 맞춘 XML을 준비해 두고 XML만 변경해서 배포하면 된다.
참조 : [자바 ORM 표준 JPA 프로그래밍]