nu_s

[JPA] JPQL 🐶 본문

JPA

[JPA] JPQL 🐶

woochii 2023. 10. 26. 12:27
728x90
반응형

JPQL(Java Persistence Query Language)

  • JPA를 사용하면 엔티티 객체를 중심으로 개발한다.
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색한다.
  • 모든 데이터를 객체로 변환해서 검색하는 것은 불가능하기 때문에, 필요한 데이터만 불러오려면 검색 조건이 포함 된 SQL이 필요하다.
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
  • SQL과 문법이 유사하다.
  • JPQL은 엔티티 객체를 대상으로 쿼리를 만든다.

JPQL 예시

//검색
List<Member> result = em.createQuery("select m from Member m where m.name like '%hello%'", Member.class)
        .getResultList();

 

 

  • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리이다.
  • SQL을 추강화해서 특정 데이터베이스 SQL에 의존하지 않는다.
  • JPQL은 결국 SQL로 변환된다.

 

1. JPQL - 기본 문법과 기능

  • select m from Member as m where m.age > 18
  • 엔티티와 속성은 대소문자를 구분한다. (Member, age)
  • JPQL 키워드는 대소문자를 구분하지 않는다. (select, from, where)
  • 테이블 이름이 아닌 엔티티 이름을 사용한다.
  • 별칭은 필수이다.

 

1-1. TypeQuery, Query

  • TypeQuery : 반환값이 명확할 때 사용한다.
  • Query : 반환값이 명확하지 않을 때 사용한다.
TypeQuery<Member> query = em.createQuery("select m from Member m", Member.class);

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

 

1-2. 결과 조회 API

  • query.getResultList()
    • 결과가 하나 이상일 때, 리스트로 반환
    • 결과가 없으면 빈 리스트 반환
  • query.getSingleResult()
    • 결과가 정확히 하나일 때, 단일 객체 반환
    • 결과가 없으면 javax.persistence.NoResultException
    • 둘 이상이면 javax.persistence.NonUniqueResultException

1-3. 파라미터 바인딩

em.createQuery("select m from Member m where m.username = :username", Member.class)
        .setParameter("username", usernameParam)

 

1-4. 프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것이다.
  • 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(기본 데이터 타입)

여러 값 조회

  • SELECT m.username, m.age FROM Member m
  1. Query 타입으로 조회
  2. Object[] 타입으로 조회
  3. new 명령어로 조회
    • 단순 값을 DTO로 바로 조회
    • SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
    • 패키지명을 포함한 전체 클래스명 입력
    • 순서와 타입이 일치하는 생성자가 필요하다.

1-5. 페이징 API

  • JPA는 페이징을 다음 두 API로 추상화한다.
    1. setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
    2. setMaxResult(int MaxResult) : 조회할 데이터 수

1-6. 조인

  • 내부 조인
    SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인
    SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인
    SELECT count(m) FROM Member m, Team t WHERE m.username = t.name

1-6-1. ON 절

  • ON절을 활용한 조인 (JPA 2.1부터 지원)
    1. 조인 대상 필터링
    2. 연관관계 없는 엔티티 외부 조인 (하이버네이트 5.1부터)
--1.조인 대상 필터링
--회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'A'

--2.연관관계 없는 엔티티 외부 조인
--회원의 이름과 팀의 이름이 같은 대상 외부 조인
SELECT m, t FROM Member m LEFT JOIN Team t ON m.username = t.name

 

1-7. 서브쿼리

 1-7-1. 서브쿼리 지원 함수

  • [NOT] EXISTS (subquery) : 서브쿼리에 결과가 존재하면 참
    • {ALL | ANY | SOME} (subquery)
    • ALL : 모두 만족하면 참
    • ANY, SOME : 같은 의미, 조건을 하나라도 만족하면 참
  • [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
--팀A 소속인 회원
select m from Member m where exists (select t from m.team t where t.name = '팀A')

--전체 상품 각각의 재고보다 주문량이 많은 주문들
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)

1-7-2. 서브쿼리의 한계

  • JPA는 WHERE, HAVING절에서만 서브쿼리를 사용할 수 있다.
  • SELECT절도 가능하다.(하이버네이트에서 지원)
  • FROM절의 서브쿼리는 하이버네이트6부터 지원한다.

1-8. 조건식

1-8-1. CASE식

--기본 CASE 식
select
    case when m.age <= 10 then '학생요금'
         when m.age >= 60 then '경로요금'
         else '일반요금'
    end
from Member m

--단순 CASE 식
select
    case t.name
        when '팀A' then '인센티브110%'
        when '팀A' then '인센티브120%'
        else '인센티브 105%'
    end
from Team t

1-8-2. COALESCE

  • 하나씩 조회해서 null이 아니면 반환
--사용자 이름이 없으면 이름 없는 회원 반환
select coalesce(m.username, '이름 없는 회원') from Member m

1-8-3. NULLIF

  • 두 값이 같으면 null반환, 다르면 첫번째 값 반환
--사용자 이름이 '관리자'면 null반환, 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m

1-8-4. 그 외의 기본 함수

  • CONCAT : 문자열 더하기
  • SUBSTRING : 문자열 자르기
  • TRIM : 공백 제거
  • LOWER, UPPER : 대문자, 소문자 변환
  • LENGTH : 길이 반환
  • LOCATE : 찾는 문자열의 위치 반환
  • ABS, SQRT, MOD : 절댓값, 제곱근, 나머지
  • SIZE, INDEX : 크기, 인덱스

2. 경로 표현식

2-1. 경로 표현식

  • .(점)을 찍어 객체 그래프를 탐색하는 것
  • 용어 정리
    • 상태 필드(state field) : 단순히 값을 저장하기 위한 필드
    • 연관 필드(association field) : 연관관계를 위한 필드
      • 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
      • 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
select m.username    -- 상태 필드
from Member m
    join m.team t    -- 단일 값 연관 필드
    join m.orders o  -- 컬렉션 값 연관 필드
    where t.name = '팀A'

2-2. 경로표현식 특징

  • 상태 필드 : 경로 탐색의 끝, 더 이상 탐색 불가
  • 단일 값 연관 경로 : 묵시적 내부 조인(inner join)발생, 더 탐색 가능
  • 컬렉션 값 연관 경로 : 묵시적 내부 조인 발생, 더 이상 탐색 X
    • FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능하다.

2-3. 명시적 조인과 묵시적 조인

  • 명시적 조인 : join 키워드를 직접 사용하는 것
    • select m from Member m join m.team t
  • 묵시적 조인 : 경로 표현식에 의해 묵시적으로 SQL 조인 발생 (내부 조인만 가능)
    • select m.team from Member m

경로 탐색을 사용한 묵시적 조인 시 주의사항

  • 항상 내부 조인
  • 컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야 한다.
  • 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM절에 영향을 줌
  • 가급적 묵시적 조인 대신에 명시적 조인 사용

3. 페치 조인(fetch join)

3-1. 페치 조인

  • SQL 조인 종류가 아니다.
  • JPQL에서 성능 최적화를 위해 제공하는 기능이다.
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다.
  • join fetch 명령어를 사용한다.

3-2. 엔티티 페치 조인

  • 회원을 조회하면서 연관된 팀도 함께 조회한다.
  • SQL을 보면 회원 뿐만 아니라 팀도 함께 SELECT 한다.
select m from Member m join fetch m.team

3-3. 컬렉션 페치 조인

  • 일대다 관계, 컬렉션 페치 조인
select t from Team t join fetch t.members
  • 컬렉션 페치 조인 사용 시 값이 중복될 수 있다.

3-4. 페치 조인과 DISTINCT

  • DISTINCT가 추가로 애플리케이션에서 중복 제거를 시도한다.
  • 같은 식별자를 가진 엔티티를 제거한다.
  • 하이버네이트6 부터는 DISTINCT 명령어를 사용하지 않아도 자동 중복 제거가 된다.

3-5. 페치 조인과 일반 조인의 차이

  • 일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않는다.
  • JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.
  • 단지 SELECT 절에 저장한 엔티티만 조회한다.
  • 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다. (즉시 로딩)
  • 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.

3-6. 페치 조인의 특징과 한계

  • 페치 조인 대상에는 별칭을 줄 수 없다.
    • 하이버네이트는 가능하지만, 가급적 사용하지 않는다.
  • 둘 이상의 컬렉션은 페치 조인 할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하다.
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징한다. (매우 위험)
  • 연관된 엔티티들을 SQL 한 번으로 조회한다. - 성능 최적화
  • 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다.
    • @OneToMany(fetch = FetchType.LAZY) // 글로벌 로딩 전략
  • 실무에서 글로벌 로딩 전략은 모두 지연 로딩이다.
  • 최적화가 필요한 곳에 페치 조인을 적용한다.

 

정리

  • 모든 것을 페치 조인으로 해결할 수 는 없다.
  • 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 페치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적이다.

4. 다형성 쿼리

4-1. TYPE

  • 조회 대상을 특정 자식으로 한정한다.
select i from Item i where type(i) IN (Book, Movie)

4-2. TREAT

  • 자바의 타입 캐스팅과 유사하다.
  • 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다.
select i from Item i where treat(i as Book).author = 'kim'

5. 엔티티 직접 사용

  • JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용한다.
select count(m.id) from Member m --엔티티의 아이디를 사용
select count(m) from Member m    --엔티티를 직접 사용

-- 둘 다 같다.

6. Named 쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 JPQL이다.
  • 정적 쿼리
  • 어노테이션, XML에 정의할 수 있다.
  • 애플리케이션 로딩 시점에 초기화 한 후 재사용한다.
  • 애플리케이션 로딩 시점에 쿼리를 검증한다.

6-1. 어노테이션에 정의

@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username")
public class Member {
    ...
}

List<Member> resultList =
    em.createQuery("Member.findByUsername", Member.class)
        .setParameter("username", "회원1")
        .getResultList();

6-2. XML에 정의

<entity-mappings ...>
    <named-query name="Member.findByUsername">
        <query><![CDATA[
            ... //쿼리가 들어온다.
     ]]></query>
    </named-query>
<entity-mappings>

 

Named 쿼리 환경에 따른 설정

  • XML이 항상 우선권을 가진다.
  • 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다.

7. 벌크 연산

  • 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
  • JPA 변경 감지 기능으로 실행하기엔 SQL이 너무 많이 실행된다.
    1. 재고가 10개 미만인 상품을 리스트로 조회한다.
    2. 상품 엔티티의 가격을 10% 증가한다.
    3. 트랜잭션 커밋 시점에 변경감지가 동작한다.
  • 변경된 데이터가 100건이라면 100번의 UPDATE SQL이 실행된다.
int resultCount = 
    em.createQuery("
        update Product p
        set p.price = p.price * 1.1
        where p.stockAmount < :stockAmount")
    .setParameter("stockAmount", 10)
    .executeUpdate();
  • 쿼리 한 번으로 여러 테이블 로우를 변경한다.
  • executeUpdate()의 결과는 영향받은 엔티티의 수를 반환한다.
  • UPDATE, DELETE를 지원한다.
  • INSERT(insert into .. select, 하이버네이트 지원)

주의

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 실행한다.
  • 제대로 사용하려면 
    1. 벌크 연산을 먼저 실행
    2. 벌크 연산 수행 후 영속성 컨텍스트 초기화

출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편

728x90
반응형