본문 바로가기
JPA/JPA

[JPA] JPQL - 기본 문법

by 걸어가는 신사 2022. 1. 7.

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

댓글