본문 바로가기

Tech/JPA

[JPA] JPQL 기본 문법

반응형

1. JPQL

JPQL 은 SQL 을 추상화하여 사용하는 객체지향 쿼리 언어이다. 따라서 테이블을 대상으로 하지 않고 엔티티 객체를 대상으로 쿼리를 수행한다. 또한 추상화를 통해서 특정 데이터베이스 SQL 에 의존되지 않게 개발할 수 있다. 실제 수행할 때는 JPQL 로 작성한 쿼리가 매핑정보 등을 통해서 SQL 로 변환되어 DB 에 수행된다.

2. JPQL 기본 문법

JPQL 의 문법은 SQL 과 유사하다. 다만 다른점은 테이블의 이름이 아니라 엔티티의 이름을 사용한다는 것이다. 아래의 예시에서 Member 는 테이블이 아닌 엔티티 클래스의 이름이다.

 

// Member 는 테이블이 아닌 엔티티이다.
em.createQuery("select m from Member as m where m.age > 18", Member.class);

 

JPQL 에서 엔티티와 엔티티의 속성 (Member, age) 은 대소문자를 구분한다. 하지만 JPQL 키워드 (SELECT, from, where) 은 대소문자를 구분하지 않는다. 그리고 엔티티를 사용하는 경우 엔티티의 별칭을 무조건 지정해주어야 한다. 위의 예시에서도 Member 엔티티의 별칭을 m 으로 지정해주었다. (as 키워드는 제외해도 된다.)

- TypeQuery, Query

JPQL 로 작성한 쿼리는 두가지 타입 TypeQuery, Query 로 구분된다. 두 타입은 반환 타입이 명확한지 여부에 따라서 결정된다.

1) TypeQuery

반환 타입이 명확할 때 사용한다.

 

TypeQuery<Member> query = em.createQuery("select m from Member m", Member.class);

2) Query

반환 타입이 명확하지 않을 때 사용한다.

 

Query query = em.createQuery("select m.username, m.age from Member m");

- 결과 조회 APi

쿼리의 결과를 받는 메서드는 getResultList() getSingleResult() 두가지가 있다.

 

- query.getResultList(): 쿼리의 결과를 리스트로 반환받을 수 있다.
- query.getSingleResult(): 쿼리의 결과가 하나일 때, 단일 객체를 반환한다.
                                             결과가 없으면 javax.persistence.NoResultException,
                                             결과가 둘 이상이면 javax.persistence.NonUniqueResultException 을 반환한다.

- 파라미터 바인딩

JPQL 쿼리에서 ':' 키워드를 사용하여 파라미터 바인딩 가능하다. 위치 기반으로도 사용할 수 있지만, 위치 기반은 변경에 취약하기 때문에 사용하지 않는 것을 권장한다.

 

// 쿼리의 :age 에 10 이 들어간다.
em.createQuery("select m from Member as m where m.age > :age")
    .setParameter("age", "10");

3. 프로젝션 (SELECT)

프로젝션은 SELECT 절을 통해서 조회할 대상을 지정하여 엔티티의 일부 속성만 가져오는 것이다. 프로젝션의 대상으로는 엔티티, 임베디드 타입, 스칼라 타입 (숫자, 문자 등 기본 데이터 타입) 등을 지정할 수 있다.

 

# 엔티티 프로젝션 - Member 엔티티 전체를 조회한다.
SELECT m FROM Member m

# 엔티티 프로젝션 - Member 엔티티와 연관관계에 있는 Team 엔티티를 조회한다.
SELECT m.team FROM Member m

# 임베디드 타입 프로젝션 - Member 엔티티의 임베디드 타입 속성 address 를 조회한다.
SELECT m.address FROM Member m

# 스칼라 타입 프로젝션 - Member 엔티티의 기본 타입 속성 username, age 를 조회한다.
SELECT m.username, m.age FROM Member m

# 중복은 DISTINCT 로 제거한다.

- 여러 값을 반환하는 조회 쿼리

쿼리의 결과가 하나의 값이 아니라 여러 개의 값을 반환하는 경우도 있다. 이러한 경우에는 다음의 방법들로 결과를 반환받을 수 있다.

 

query = "select m.username, m.age from Member as m";

// 1. Query 타입으로 조회
List result1 = em.createQuery(query).getResultList();
for(Object obj : result1) {
    Object[] o = (Object[]) obj;
    System.out.println(Arrays.toString(o));
}

// 2. Object[]  타입으로 조회
List<Object[]> result2 = em.createQuery(query).getResultList();
for(Object[] o : result2) {
    System.out.println(Arrays.toString(o));
}

// 3. new 키워드로 조회 - 결과 객체를 변환할 DTO 클래스의 패키지 + 클래스 명의 경로를 입력하여 단순 값을 DTO 로 바로 변환하여 조회한다. 이때 결과와 DTO 의 타입과 순서가 동일해야햔다.
List<MemberDTO> result3 = em.createQuery("select new jpaproject.test.dto.UserDTO(m.username, m.age) from Member as m").getResultList();
for(MemberDTO member : result3) {
    System.out.println(member.toString());
}

 

4. 페이징

JPA 에서는 DB 에서 데이터를 조회할 때 페이징을 사용하여 원하는 구간의 데이터를 가져올 수 있도록 API 를 제공한다.

 

- setFirstResult(int startPosition): 조회 시작 위치를 지정한다. 인덱스는 0 부터 시작한다.
- setMaxResult(int maxResult): 조회할 데이터의 최대 개수를 지정한다.

 

// 해당 쿼리로 조회하면 결과의 10번째 데이터부터 최대 20개의 데이터를 리스트로 반환한다.
List<Member> members = em.createQuery("select m from Member m order by m.age desc", Member.class)
                            .setFirstResult(10)
                            .setMaxResults(20)
                            .getResultList();

5. 조인

# 내부조인 (INNER JOIN)
SELECT m FROM Member m [INNER] JOIN m.team t;

# 외부조인 (OUTER JOIN)
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t;

# 세타조인 (THETA JOIN)
SELECT COUNT(m) FROM Member m, Team t WHERE m.username = t.name;

- ON 절

JPA 2.1 부터는 ON 절을 활용한 조인문을 지원해준다.

1) 조인 대상 필터링

# 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'

2) 연관관계 없는 엔티티 외부 조인

# JPQL
SELECT m, t
  FROM Member m LEFT JOIN Team t
    ON m.username = t.name

# SQL
SELECT m.*, t.*
  FROM Member m LEFT JOIN Team t
    ON m.username = t.name

6. 서브 쿼리

JPQL 에서는 괄호를 사용하여 서브 쿼리를 구현할 수 있다.

 

SELECT m
  FROM Member m
 WHERE m.age > (SELECT avg(m2.age) FROM Member m2)

- 서브 쿼리 지원함수

JPQL 의 서브 쿼리와 사용할 수 있는 함수들로는 EXISTS, ALL, ANY, SOME, IN 이 있다.

1) EXISTS

서브쿼리에 결과가 존재하면 참을 반환한다.

 

SELECT m
  FROM Member m
 WHERE EXISTS (SELECT t FROM m.team t where t.name = "A")

2) ALL, ANY, SOME

ALL 은 서브쿼리의 결과가 모두 조건에 부합하면, ANY 와 SOME 은 하나라도 부합하면 참을 반환한다.

 

SELECT t
  FROM Team t
 WHERE 10 < ALL (SELECT m.age FROM t.members m)

3) IN

서브쿼리의 결과 중 하나라도 같은 것이 있는 경우에 참을 반환한다.

 

SELECT t
  FROM Team t
 WHERE 'jamie' IN (SELECT m.name FROM t.members m)

 

JPA 는 WHERE, HAVING 절에서만 서브 쿼리를 사용할 수 있다. FROM 절에서의 서브쿼리는 JPA 에서는 불가능하다. 그렇기 때문에 대부분의 경우는 조인을 사용하여 해결해야 한다. (하이버네이트에서는 SELECT 절에서도 서브쿼리를 사용할 수 있다.)

7. JPQL 기타 표현

JPQL 에서는 java 와 다양한 타입 표현을 다음과 같이 쿼리에서 표현할 수 있다.

 

- 문자: 'HELLO'

- 숫자: 10L (long), 10D (double), 10F (float)

- boolean: TRUE, FALSE

- ENUM: 패키지명을 포함하여 입력 - network.status.SUCCESS

- 엔티티 타입: 해당 객체의 타입으로 필터링 하는 경우 - TYPE(m) = Member // m 이 Member 타입일때

 

EXISTS, IN, AND, OR, NOT, =, >, >=, <, <=, <>, BETWEEN, LIKE, IS NULL 등등 SQL 문법의 표현을 동일하게 사용할 수 있다.

8. 조건식

JPQ 에서는 CASE 문을 사용한 조건식을 지원한다.

- 기본 CASE 식

case 에 비교식을 넣어서 조건을 설정

 

select
  case when s.score < 60 then 'F'
        when s.score < 70 then 'D'
        when s.score < 80 then 'C'
        when s.score < 90 then 'B'
        else 'A'
   end
  from Students s

- 단순 CASE 식

case 문에서 조건이 특정한 값 하나를 지정하여 같은 값인지 비교하는 경우

 

select
  case t.name
    when 'A' then '100'
    when 'F' then '0'
    else '50'
   end
from Team t

- COALESCE

하나씩 조회해서 NULL 이 아니면 반환

 

select coalesce(m.username, "no name")
  from Member m

- NULLIF

두값이 같으면 null 반환, 다르면 첫번째 값 반환

 

select nullif(m.grade, 'F')
  from Member m

9. JPQL 기본함수

위에서 기술한 여러 기능들 외에도 JPQL 에서는 여러가지 함수들을 제공해준다. 대부분 기존 SQL 에서 사용하는 함수들과 동일한 기능들로 CONCAT, SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE, ABS, SQRT, MOD, SIZE, INDEX 등이 있다.

- 사용자 정의 함수 호출

하이퍼네이트에서는 사용자가 특정 함수들을 정의하여서 사용할 수 있다. 주로 각 DB 에 종속적인 함수를 사용하기 위해서 등록할 때 사용한다. 이때는 사용하는 DB 의 방언을 상속받고 사용자 정의 함수로 등록한다.

반응형

'Tech > JPA' 카테고리의 다른 글

[JPA] LazyInitializationException 과 OSIV  (1) 2024.02.07
[JPA] JPQL 경로표현식과 fetch join  (0) 2022.06.19
[JPA] 값 타입  (0) 2022.06.04
[JPA] 영속성 전이 (CASCADE)  (0) 2022.05.26
[JPA] 프록시와 지연로딩  (0) 2022.05.23