Chapter10. 객체지향 쿼리 (JPQL)

김신영·2022년 12월 24일
0

JPA

목록 보기
8/14
post-thumbnail

객체지향 쿼리 언어 소개

JPQL

  • 엔티티 객체를 조회하는 객체지향 쿼리
  • SQL을 추상화 해서 특정 데이터베이스에 의존하지 않음
  • SQL보다 간결

Criteria

  • JPQL을 생성하는 빌더 클래스
  • 문자가 아닌 query.select(m).where(...) 처럼 프로그래밍 코드로 JPQL을 작성 할 수 있음
  • 컴파일 시점에 오류를 발견할 수 있음
  • IDE를 사용하면 코드 자동완성을 지원
  • 동적 쿼리를 작성하기 편함
  • QueryDSL에 비해 다소 사용성이 복잡하다.

QueryDSL 소개

  • Criteria처럼 JPQL 빌더 역할
  • 코드 기반이면서 단순하고 사용하기 쉬움

Native SQL

  • SQL을 직접 사용할 수 있는 기능을 지원

JPQL

SELECT 문

  • 엔티티와 속성은 대소문자 구분
    • JPQL 키워드는 대소문자 구분하지 않음
  • 엔티티 이름 사용
  • 별칭은 필수
select m from Member m where m.username = 'rolroralra'

TypeQuery, Query

  • JPQL을 실행하려면 쿼리 객체를 만들어야 함
  • 반환할 타입이 명확하면 TypedQuery
  • 그렇지 않으면 Query
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);

List<Member> resultList = query.getResultList();
Query query1 = em.createQuery("select m.username, m.age from Member m");
Query query2 = em.createQuery("select m.username from Member m");

List<Object[]> resultList1 = query1.getResultList();
List<Object> resultList2 = query2.getResultList();

결과 조회

Query::getResultList

  • 결과가 없으면 빈 컬렉션을 반환한다.

Query:getSingleResult

  • 결과가 없으면, javax.persistence.NoResultException
  • 결과가 1개보다 많으면, javax.persistence.NonUniqueResultException

파라미터 바인딩

Named Parameter

  • 앞에 : 를 사용
List<Member> members = em.createQuery(
	"select m from Member m where m.username = :username", Member.class)
	.setParameter("username", "rolroralra")
	.getResultList();

Positional Parameter

  • ? 다음에 위치 값을 주면 됨
  • 위치 값은 1부터 시작
List<Member> members = em.createQuery(
	"select m from Member m where m.username = ?1", Member.class)
	.setParameter("username", "rolroralra")
	.getResultList();

프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것

엔티티 프로젝션

  • 조회한 엔티티는 영속성 컨텍스트에서 관리
SELECT m FROM Member m
SELECT m.team FROM Member m

임베디드 프로젝션

  • 임베디드 타입은 엔티티 타입이 아닌 값 타입이다.
  • 임베디드 타입은 영속성 컨텍스트에서 관리되지 않음
List<Address> addresses = em.createQuery(
	"select m.address from Member m", Address.class)
	.getResultList();

스칼라 타입 프로젝션

List<String> usernames = em.createQuery(
	"select m.username from Member m", String.class)
	.getResultList();
	
Double orderMountAvg = em.createQuery(
	"select AVG(o.orderAmount) from Order o", Double.class)
	.getSingleResult();

여러 값 조회

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

List<Object[]> resultList = query.getResultList();

NEW 명령어

  • 패키지 명을 포함한 전체 클래스 명을 입력해야 한다.
  • 순서와 타입이 일치하는 생성자가 필요하다.
TypedQuery<UserDTO> query = em.createQuery("select new com.example.UserDTO(m.username, m.age) from Member m", UserDTO.class);

페이징 API

List<Member> members = em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class)
	.setFirstResult(10)
	.setMaxResults(20)
	.getResultList();
  • setFirstResult(int startPosition)
    • 조회 시작 위치 (0부터 시작)
  • setMaxPesults(int maxResult)
    • 조회할 데이터 수

집합과 정렬

  • COUNT
  • MAX, MIN
  • AVG
  • SUM
  • GROUP BY, HAVING
  • ORDER BY
    - ASC
    - DESC

JPQL 조인

내부 조인

SELECT m FROM Member m [INNER] JOIN m.team t WHERE t.name = :teamName

외부 조인

SELECT m FROM Member m LEFT [OUTER] JOIN m.team t

컬렉션 조인

SELECT t, m FROM Team t LEFT JOIN t.members m
  • 일대다 관계나 다대다 관계처럼 컬렉션을 사용 하는 곳에서 사용하는 조인

세타 조인

SELECT COUNT(m) FROM Member m, Team t
WHERE m.username = t.name
  • inner join 만 가능하다.

페치 조인

  • JPQL에서 성능 최적화를 위해 제공하는 기능
  • 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능
  • fetch join 의 경우, 별칭을 사용할 수 없다.
  • 하지만 hibernate의 경우, 별칭을 사용할 수 있다.
select m from Member m join fetch m.team

select t from  Team t join fetch t.members

select distinct t from Team t join fetch t.members

페치 조인과 일반 조인의 차이

  • JPQL은 결과를 반환할 때 연관관계까지 고려하지 않는다. 단지 SELECT 절에 지정한 엔티티만 조회할뿐
  • 페치조인을 사용 시 연관된 엔티티도 함께 조회

페치 조인의 특징과 한계

  • 페치 조인을 사용 시 한번의 SQL로 연관된 엔티티들을 함께 조회할 수 있어 SQL 호출 횟수를 줄여 성능을 최적화 할 수 있음
  • 페치 조인은 글로벌 로딩 전략보다 우선함
    • 엔티티에 직접 적용하는 로딩 전략은 애플리케이션 전체에 영향을 미치므로 글로벌 로딩 전략이라 부름
  • 글로벌 로딩 전략은 지연 로딩을 사용하고 최적화가 필요하면 페치 조인을 적용하는 것이 효과적
  • 페치 조인 대상에는 별칭을 줄 수 없음
  • 둘 이상의 컬렉션 페치할 수 없음
  • 컬렉션 페치 조인 시 페이징 API 사용할 수 없음

경로 표현식

  • 상태 필드 : 단순히 값을 저장하기 위한 필드
    • 필드 or 프로퍼티
      - t.username, t.age
  • 연관 필드 : 연관관계를 위한 필드, 임베디드 타입 포함
    • 필드 or 프로퍼티
    • 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티
      - m.team
    • 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션
      - m.orders
  • 상태 필드 경로: 경로 탐색의 끝. 더 이상 탐색 불가능
  • 단일 값 연관 경로: 묵시적으로 내부 조인이 일어남. 단일 값 연관 경로는 계속 탐색할 수 있음
  • 컬렉션 값 연관 경로: 묵시적으로 내부 조인이 일어남. 더 이상 탐색할 수 없음. 단 FROM 절에서 조인을 통해 별칭을 얻으면 별칭으로 탐색할 수 있음

서브 쿼리

  • WHERE, HAVING 절에서만 사용 가능
  • EXISTS
  • ALL | ANY | SOME
    • 비교 연산자와 같이 사용 { = | > | ≥ | ≤ | <> }
      • ALL: 조건을 모두 만족 시 참
      • ANY 혹은 SOME: 조건이 하나라도 만족하면 참
  • IN
    • 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
  • 조건식
  • 다형성 쿼리
    • JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회함
  • 사용자 정의 함수 호출
  • 기타 정리
  • 엔티티 직접 사용
  • Named 쿼리: 정적 쿼리
    • 동적 쿼리
      • em.create("select...") 처럼 JPQL을 문자로 완성하여 직접 넘기는 것
      • 런타임에 특정 조건에 따라 JPQL 을 동적으로 구성 할 수 있음
    • 정적 쿼리
      • 미리 정의한 쿼리에 이름을 부여하여 필요할 때 사용할 수 있는데 이것을 Named 쿼리라고 함
      • 한번 정의 시 변경할 수 없는 정적인 쿼리

Criteria

  • JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API
  • Criteria를 사용하면 코드가 복잡하고 장황해짐

QueryDSL

  • Criteria의 단점을 개선하여 쿼리를 문자가 아닌 코드로 작성해도 간결하고 모양이 쿼리와 비슷하게 개발 할 수 있는 프로젝트

  • 오픈소스 프로젝트

  • 데이터를 조회하는 데 기능이 특화되어있음

  • 시작

    • JPAQuery 객체 생성을 해야하는데 이 때 엔티티 메니저를 생성자에 넘겨줌
    • 사용할 쿼리 타입을 생성하는데 생성자에게는 별칭을 줌
  • 기본 Q 생성

    • 쿼리 타입(Q)은 아래와 같이 기본 인스턴스를 보관
    • 하지만 같은 엔티티를 조인 하거나 같은 엔티티를 서브쿼리에 사용하면 같은 별칭이 사용되므로 이 때는 별칭을 직접 지정하여 사용
  • 검색 조건 쿼리

    • QueryDSL 의 where 절에는 and 나 or 사용 가능
  • 결과 조회

    • uniqueResult(): 조회 결과가 한 건일 때 사용
      • 조회 결과 없을 시 null 반환, 결과 하나 이상이면 NonUniqueResultException 예외 발생
  • singleResult() : uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환

  • list() : 결과가 하나 이상일 때 사용, 결과 없을시 빈 컬렉션 반환

  • 페이징 & 정렬

  • 그룹

  • 조인

    • innerJoin(join), leftJoin, rightJoin, fullJoin 사용 및 JPQL 의 성능최적화를 위한 fetch 조인도 사용할 수 있음
    • join(조인대상, 별칭으로 사용할 쿼리 타입)
  • 서브 쿼리

    • com.mysema.query.jpa.JPASubQuery를 생성하여 사용
    • 서브쿼리 결과가 하나면 unique(), 여러 건이면 list() 사용
  • 프로젝션 결과 반환

    • select 절에 조회 대상을 지정 하는 것을 프로젝션이라 함

    • 프로젝션 대상이 하나

    • 여러 컬럼 반환과 튜플

      • 튜플 사용
  • 빈 생성

    • com.mysema.query.types.Projections 사용
    • 특정 객체(ex. DTO) 로 받고 싶을 때 사용
    • 프로퍼티 접근
    • 필드 직접 접근
    • 생성자 사용
  • 수정, 삭제, 배치 쿼리

    • JPQL 배치 처럼 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리
  • 동적 쿼리

    • com.mysema.query.BooleanBuilder 사용하여 특정 조건에 따른 동적 쿼리 생성
  • 메소드 위임

    • QueryDelegate 사용 시 쿼리 타입에 검색 조건을 직접 정의 가능

Native SQL

  • JPQL은 특정 데이터베이스에 종속적인 기능은 지원하지 않음

  • 특정 데이터베이스만 지원하는 함수, 문법, SQL 쿼리 힌트

  • 인라인 뷰(From 절에서 사용하는 서브쿼리), UNION, INTERSECT

  • 스토어드 프로시저

  • JPQL을 사용할 수 없을 때 JPA는 SQL을 직접 사용할 수 있는 기능을 제공하는데 이를 네이티브 SQL이라 함

  • 네이티브 SQL을 사용하면 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있음

10.6 객체지향 쿼리 심화

  • 벌크 연산
    • 한 번에 여러 데이터를 수정할 수 있음
    • 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
    • executeUpdate() 사용
  • 영속성 컨텍스트와 JPQL
  • JPQL과 플러시 모드

10.7 정리

  • JPQL은 SQL을 추상화 했으므로 특정 데이터베이스 기술에 의존하지 않음
  • Criteria나 QueryDSL은 JPQL을 만들어주는 빌더역할을 할 뿐 핵심은 JPQL을잘 알아야 함
  • Criteria, QueryDSL 을 사용 시 동적쿼리를 편하게 작성 할 수 있음
  • 최대한 JPQL을 사용하고, 그래도 방법이 없을 때 네이티브 SQL을 사용하자
  • JPQL은 대량의 데이터 수정,삭제 하는 벌크 연산을 지원함
profile
Hello velog!

0개의 댓글