JPA가 공식 지원하는 기능
가장 중요한 객체지향 쿼리 언어다.
JPQL을 사용하면 JPA는 이 JPQL을 분석한 다음 적절한 SQL을 만들어 데이터베이스를 조회한다. 그리고 조회한 결과로 엔티티 객체를 생성해서 반환한다.
JPQL의 특징
@Entity(name="Member")
public class Member {
@Column(name="name")
private String username;
...
}
이 회원 엔티티를 대상으로 JPQL을 사용하는 예제를 보자.
String jpql = "select m from Member as m where m.username = 'kim'";
List<Member> resultList =
em.createQuery(jpql, Member.class).getResultList();
회원이름이 kim인 엔티티를 조회한다. JPQL에서 Member는 엔티티 이름이다. 그리고 m.username은 테이블 컬럼명이 아니라 엔티티 객체의 필드명이다.
실제 실행된 SQL은 다음과 같다.
select
member.id as id,
member.age as age,
member.team_id as team,
member.name as name
from
Member member
where
member.name='kim'
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 ::= update_절 [where_절]
delete_문 ::= delete_절 [where_절]
SELECT m FROM Member AS m where m.username = 'Hello'
Member AS m을 보면 Member에 m이라는 별칭을 주었다. JPQL은 별칭을 필수로 사용해야 한다.Member m처럼 사용해도 된다.작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다. 쿼리 객체는 TypeQuery와 Query가 있는데 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery 객체를 사용하고, 반환 타입을 명확하게 지정할 수 없으면 Query 객체를 사용하면 된다.
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
for(Member member : resultList) {
System.out.println("member = " + member);
}
Query query =
em.createQuery("SELECT m.username, m.age FROM Member m");
List<Member> resultList = query.getResultList();
for(Object o : resultList) {
Object[] result = (Object[]) o; //결과가 둘 이상이면 Object[] 반환
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
}
JPQL은 위치 기준 파라미터 바인딩과 이름 기준 파라미터 바인딩을 지원한다.
파라미터를 이름으로 구분하는 방법이다. 앞에 :를 사용한다.
String usernameParam = "User1";
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", usernameParam);
List<Member> resultList = query.getResultList();
위치 기준 파라미터를 사용하려면 ? 다음에 위치 값을 주면 된다. 위치 값은 1부터 시작한다.
위치 기준 파라미터 방식보다는 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확하다.
List<Member> members =
em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
.setParameter(1, usernameParam)
.getResultList();
SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라 하고 [SELECT {프로젝션 대상} FROM]으로 대상을 선택한다. 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다. 스칼라 타입은 숫자, 문자 등 기본 데이터 타입을 뜻한다.
페이징 처리용 SQL을 작성하는 일은 지루하고 반복적이다. 데이터베이스마다 페이징을 처리하는 SQL 문법이 다른 것도 문제이다.
JPA는 페이징을 다음 두 API로 추상화했다.
집합은 집합함수와 함께 통계 정보를 구할 때 사용한다. 예를 들어 다음 코드는 순서대로 회원수, 나이 합, 평균 나이, 최대 나이, 최소 나이를 조회한다.
SQL 조인과 기능은 같고 문법만 약간 다르다.
INNER JOIN을 사용한다. INNER는 생략할 수 있다.
SELECT m
FROM Member m INNER JOIN m.team t
where t.name = :teamName
기능상 SQL의 외부 조인과 같다. OUTER는 생략 가능해서 보통 LEFT JOIN으로 사용한다.
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 컬렉션 조인이라 한다.
SELECT t, m FROM Team t LEFT JOIN t.members m
여기서 t LEFT JOIN t.members는 팀과 팀이 보유한 회원목록을 컬렉션 값 연관 필드로 외부 조인했다.
WHERE 절을 사용해서 세타 조인을 할 수 있다. 세타 조인은 내부 조인만 지원한다.
세타 조인을 사용하면 전혀 관계 없는 엔티티도 조인할 수 있다.
SELECT count(m) from Member m, Team t
where m.username = t.name
JPA 2.1부터 조인할 때 ON 절을 지원한다. ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있다. 보통 ON 절은 외부 조인에서만 사용한다.
모든 회원을 조회하면서 회원과 연관된 팀도 조회해보자.
select m, t from Member m
left join m.team t on t.name = 'A'
JPQL에서 성능 최적화를 위해 제공하는 기능이다. 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는데, join fetch 명령어로 사용할 수 있다.
그리고 페치 조인은 별칭을 사용할 수 없다.
페치 조인 문법
[LEFT [OUTER] | INNER] JOIN FETCH 조인경로
페치 조인을 사용해서 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회해보자.
select m
from Member m join fetch m.team
일대다 관계인 컬렉션을 페치 조인해보자.
select t
from Team t join fetch t.members
where t.name = '팀A'
JPQL의 DISTINCT 명령어는 SQL에 DISTINCT를 추가하는 것은 물론이고 애플리케이션에서 한 번 더 중복을 제거한다.
select distinct t
from Team t join fetch t.members
where t.name = '팀A'
경로 표현식: .을 찍어 객체 그래프를 탐색하는 것
JPQL에서 경로 표현식을 사용해서 경로 탐색을 하려면 다음 3가지 경로에 따라 어떤 특징이 있는지 이해해야 한다.
경로 탐색을 사용하면 묵시적 조인이 발생해서 SQL에서 내부 조인이 일어날 수 있다. 이때 주의사항은 다음과 같다.
조인이 성능상 차지하는 부분은 아주 크다. 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다는 단점이 있다. 따라서 단순하고 성능에 이슈가 없으면 크게 문제가 안 되지만 성능이 중요하면 분석하기 쉽도록 묵시적 조인보다는 명시적 조인을 사용하자.
JPQL도 SQL처럼 서브 쿼리를 지원한다. 서브 쿼리를 WHERE, HAVING 절에서만 사용할 수 있고 SELECT, FROM 절에서는 사용할 수 없다.
서브 쿼리는 다음 함수들과 같이 사용할 수 있다.