1. JPQL이란?
엔티티 객체를 조회하는 객체지향 쿼리이다.
- 문법은 SQL과 비슷하고 ANSI 표준 SQL이 제공하는 기능을 유사하게 지원한다.
- SQL은 데이터베이스 테이블을 대상으로 JPQL은 엔티티 객체를 대상으로 쿼리 한다.
- JPQL은 SQL을 추상화해서 특정 데이터베이스에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
2. JPQL 기본 문법
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
- SELECT, UPDATE, DELETE 문 사용할 수 있다.
- INSERT 문은 EntityManager.persist() 메소드를 사용하므로 JPQL이 필요 없다.
(1) Query 문
select m from Member as m where m.username = 'Hello'
- 대소문자 구분
- 엔티티와 속성은 대소문자를 구분하고 JPQL 키워드는 대소문자를 구분하지 않는다.
- Member, username은 대소문자를 구분한다.
- SELECT, FROM, AS는 대소문자를 구분하지 않는다.
- 엔티티 이름
- JPQL에서 사용한 Member는 테이블 명, 클래스 명이 아니라 엔티티 명이다.
- 엔티티 명은 @Entity(name = "")로 지정할 수 있다.
- 기본값인 클래스 명을 엔티티 명으로 사용하자.
- 별칭
- "Member as m"을 보면 Member에 m이라는 별칭을 주었다.
- JPQL은 별칭이 필수이다. (as는 생략 가능)
(2) 집합
- GROUP BY : 통계 데이터를 구할 때 특정 그룹끼리 묶어준다.
- HAVING : GROUP BY와 함께 사용하는데 GROUP BY로 그룹화한 통계 데이터를 기준으로 필터링한다.
groupby_절 :: = GROUP BY {단일값 경로 | 별칭}
having_절 :: = HAVING 조건식
select t.name from Member m LEFT JOIN m.team t GROUP BY t.name Having AVG(m.age) >= 10
- 집합 함수
함수 | 설명 |
COUNT | 결과 수를 구한다. 반환 타입 : Long |
MAX, MIN | 최대, 최소 값을 구한다. 문자, 숫자, 날짜 등에 사용한다. |
AVG | 평균값을 구한다. 숫자타입만 사용할 수 있다. 반환 타입 : Double |
SUM | 합을 구한다. 숫자타입만 사용할 수 있다. |
- 참고사항
- NULL 값은 무시하므로 통계에 잡히지 않는다.
- 만약 값이 없는데 SUM, AVG, MAX, MIN 함수를 사용하면 NULL 값이 된다. (COUNT는 0이 된다)
- DISTINCT를 집합 함수 안에 사용해서 중복된 값을 제거하고 나서 집합을 구할 수 있다.
- select COUNT( DISTINCT m.age ) from Member m
- DISTINCT를 COUNT에서 사용할 때 임베디드 타입은 지원하지 않는다.
(3) 정렬
- ORDER BY : 결과를 정렬할 때 사용한다.
- ASC : 오름차순(기본값)
- DESC : 내림차순
orderby_절 :: = ORDER BY { 상태필드 경로 | 결과 변수 [ASC | DESC] }
select m from Member m order by m.age DESC, m.username ASC
3. 서브쿼리
(1) 서브 쿼리
- JPQL도 SQL처럼 서브 쿼리를 지원한다.
- 서브 쿼리를 WHERE, HAVING 절에서만 사용할 수 있고 SELECT, FROM 절에서는 사용할 수 없다.
- 하이버네이트의 HQL은 SELECT 절의 서브 쿼리도 허용한다. (FROM 절의 서브 쿼리는 지원X)
//나이가 평균보다 많은 회원
select m from Member m where m.age > (select avg(m2.age) from Member m2)
//한 건이라도 주문한 고객
select m from Member m where (select count(o) from Order o where m = o.member) > 0
(2) 서브 쿼리 함수
(i) EXISTS
- 문법 : [NOT] EXISTS (subquery)
- 설명 : 서브 쿼리에 결과가 존재하면 참이다. NOT은 반대
//teamA 소속인 회원
select m from Member m where exists (select t from m.team t where t.name = 'teamA')
(ii) {ALL | ANY | SOME}
- 문법 : {ALL | ANY | SOME} (subquery)
- 설명 : 비교 연산자와 같이 사용한다. { = | > | >= | < | <= | <> }
- All : 조건을 모두 만족하면 참이다.
- ANY or SOME : 둘은 같은 의미다. 조건을 하나라도 만족하면 참이다.
//전체 상품 각각의 재고보다 주문량이 많은 주문들
select 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)
(iii) [NOT] IN
- 문법 : [NOT] IN (subqeury)
- 설명 : 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참이다.
- IN은 서브 쿼리가 아닌 곳에서도 사용한다.
//20세 이상을 보유한 팀
select t from Team t where t IN (select t2 from Team t2 join t2.members m2 where m2.age >= 20)
4. 쿼리 객체
- 작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다.
- 쿼리 객체는 TypeQuery와 Query 두 가지가 있다.
(1) TypeQuery
- 반환할 타입을 명확하게 지정할 수 있을 때 사용한다.
TypedQuery<Member> query = em.createQuery("select m from Member as m", Member.class);
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
for (Member member : members) {
System.out.println(member);
}
- 조회 대상이 Member 엔티티이므로 조회 대상 타입이 명확하다.
(2) Query
- 반환 타입을 명확하게 지정할 수 없을 때 사용한다.
Query query = em.createQuery("select m.username, m.age from Member m");
- 조회 타입이 String 타입의 username과 Integer 타입의 age이므로 반환 타입이 명확하지 않다.
- Query객체는 조회 대상이 둘 이상이면 Object[]를 반환하고 조회 대상이 하나면 Object를 반환한다.
List<Object[]> resultList = query.getResultList();
for (Object[] objects : resultList) {
String userName = (String) objects[0];
Integer age = (Integer) objects[1];
}
List resultList = query.getResultList();
for (Object o : resultList) {
Object[] objects = (Object[]) o;
String userName = (String) objects[0];
Integer age = (Integer) objects[1];
}
5. 결과 조회 API
(1) query.getResultList()
- 결과가 하나 이상 일 때, 리스트 반환한다.
- 결과가 없으면 빈 리스트 반환
TypedQuery<Member> query = em.createQuery("select m from Member as m", Member.class);
List<Member> resultList = query.getResultList();
(2) query.getSingleResult()
- 결과가 정확히 하나, 단일 객체 반환한다.
- 결과가 없으면 : javax.persistence.NoResultException
- 둘 이상이면 : javax.persistence.NonUniqueResultException
TypedQuery<Member> query = em.createQuery("select m from Member as m where m.id = 1L", Member.class);
Member result = query.getSingleResult();
6. 파라미터 바인딩
- JPQL은 이름 기준, 위치 기준 2가지 파라미터 바인딩을 지원한다.
(1) 이름 기준
- 파라미터를 이름으로 구분하는 방법이다.
- 이름 기준 파라미터는 앞에 :를 사용한다.
Member result = em.createQuery("select m from Member as m where m.username= :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
- :username이라는 이름 기준 파라미터를 정의한다.
- query.setParameter() 에서 username이라는 이름으로 파라미터를 바인딩한다.
(2) 위치 기준
- 파라미터를 위치로 구분하는 방법이다.
- ? 다음에 위치 값을 주면 된다. (위치 값은 1부터 시작한다.)
List<Member> members = em.createQuery("select m from Member m where m.username = ?1", Member.class)
.setParameter(1, "member1")
.getResultList();
위치 기준 파라미터 방식보다는 이름 기준 파라미터 바인딩 방식을 사용하는 것이 더 명확하다.
7. 프로젝션(Projection)
- SELECT 절에 조회할 대상을 지정하는 것을 프로젝션(projection)이라 한다.
(1) 엔티티 프로젝션
- 원하는 객체를 조회한다고 생각할 수 있다.
- 조회한 엔티티는 영속성 컨텍스트에서 관리된다.
select m from Member m
select m.team from Member m
(2) 임베디드 타입 프로젝션
- 임베디드 타입은 조회의 시작점이 될 수 없다.
String query = "select a from Address a" //Address는 임베디드 타입이므로 잘못된 쿼리
//임베디드 타입 프로젝션
List<Address> result = em.createQuery("select o.address from Order o", Address.class)
.getResultList();
- 임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.
(3) 스칼라 타입 프로젝션
- 숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입이라 한다.
List<Member> result = em.createQuery("select m.username from Member m", Member.class)
.getResultList(); //전체 회원의 이름을 조회
(4) 여러 값 조회
- 원하는 데이터들만 선택해서 조회할 수 있다.
- 객체 타입 Query, TypeQuery를 사용해서 조회할 수 있다.
//Query 사용
List resultList = em.createQuery("select m.username, m.age from Member as m")
.getResultList();
Iterator iterator = resultList.iterator();
while (iterator.hasNext()) {
Object[] result = (Object[]) iterator.next();
String username = (String) result[0];
Integer age = (Integer) result[1];
}
//TypeQuery 사용
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member as m")
.getResultList();
for (Object[] result : resultList) {
String username = (String) result[0];
Integer age = (Integer) result[1];
}
(5) New 명령어
- new 명령어를 사용해서 여러 값을 조회하는 경우보다 깔끔하게 코딩할 수 있다.
- 단순 값을 DTO로 바로 조회한다.
@Getter @Setter
public class MemberDTO {
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
private String username;
private int age;
}
- MemberDTO class를 구현한다.
List<MemberDTO> result =
em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member as m", MemberDTO.class)
.getResultList();
for (MemberDTO memberDTO : result) {
System.out.println(memberDTO.getUsername());
System.out.println(memberDTO.getAge());
}
- 패키지 명을 포함한 전체 클래스 명을 입력해 주어야 한다.
- 순서와 타입이 일치하는 생성자가 필요하다.
8. 페이징 API
- JPA는 페이징을 다음 두 API로 추상화하였다.
- setFirstResult (int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResult (int maxResult) : 조회할 데이터 수
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
- 시작 index 1부터 총 10개의 데이터를 조회한다.
- 데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있게 되었다.
- MYSQL, ORACLE, SQLServer 등의 방언을 고려해서 JPA가 처리해준다.
반응형
'JPA > JPA' 카테고리의 다른 글
[JPA] JPQL - 페치 조인(Fetch Join) (0) | 2022.01.07 |
---|---|
[JPA] JPQL - 조인(Join) (0) | 2022.01.07 |
[JPA] 값 타입 (0) | 2022.01.04 |
[JPA] 영속성 전이(CASCADE)와 고아 객체 제거 (0) | 2022.01.03 |
[JPA] 즉시 로딩과 지연 로딩 (0) | 2022.01.03 |
댓글