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 절에서는 사용할 수 없다.
서브 쿼리는 다음 함수들과 같이 사용할 수 있다.