가자공부하러!

Hibernate(8) - 객체지향 쿼리 언어(JPQL, QueryDSL 등) 본문

공부/Java

Hibernate(8) - 객체지향 쿼리 언어(JPQL, QueryDSL 등)

오피스엑소더스 2019. 11. 25. 08:11

1. JPQL(Java Persistence Query Language)

1.1. 등장배경

  - JPA를 사용하면 엔티티 객체를 중심으로 개발을 하게됨

  - 검색할 때 마다 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능

  - 필요한 데이터만 불러오려면 검색 조건이 포함된 SQL이 필요

1.2. 특징

  - SQL을 추상화한 쿼리 언어이며, SQL이 지원하는 키워드는 모두 지원

  - JPQL은 엔티티 객체를 대상으로 쿼리 수행(SQL은 DB 테이블 대상)

  - JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.

  - JPQL은 결국 SQL로 변환된다.

1.3. 사용방법과 예제

  - 기초 사용법

    > 엔티티와 속성은 대소문자 구분, JPQL 키워드(select, FROM, ...)는 대소문자 구분하지 않음

    > 테이블 이름을 사용하지 않고 엔티티 이름을 사용함

    > 별칭(alias) 필수 (select m from Member as m)

//Member는 테이블이 아니고 엔티티, 2번 째 매개변수로 엔티티 명시
List<Member> resultList = em.createQuery(
        "select m from Member m where m.username like '%kim%'", Member.class)
        .getResultList();

  - 반환타입에 따른 쿼리 저장 타입

    > 반환타입이 명확한 경우

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

 

    > 반환타입이 명확하지 않은 경우

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

  - 결과 조회 API

    > 결과가 하나 이상일 때 사용 : query.getResultList();

      - 결과가 없으면 빈 리스트 반환

    > 결과가 정확하게 하나일 때 사용 : query.getSingleResult();

      - 결과가 없으면 NoResultException

      - 결과가 둘 이상이면 NonUniqueResultException

  - 파라미터 바인딩

// 콜롱으로 지정(:username)
String inputName = "memberA";
TypedQuery<Member> query = em.createQuery("select m from Member m where m.username = :username", Member.class);
query.setParameter("username", inputName);

//메소드 체이닝 방식
Member result = em.createQuery("select m from Member m where m.username = :username", Member.class)
		.setParameter("username", inputName)
        .getSingleResult();

  - 프로젝션

    > 특징

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

    > 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(문자, 숫자 등 기본 데이터 타입)

    > 엔티티 프로젝션 : 조회할 대상을 엔티티로 지정

      - 엔티티 프로젝션을 통해 조회된 엔티티들은 영속성 컨텍스트의 관리를 받음

    > 임베디드 타입 프로젝션 : 조회할 대상을 임베디드 타입으로 지정

      - 임베디드 타입 독자적으로 가져올 수는 없고 종속된 엔티티로 부터 받아와야 함

// Address는 임베디드타입이며, Order에 종속됨
em.createQuery("select o.address from Order o", Address.class).getResultList();

//아래 쿼리처럼 사용할 수는 없음
em.createQuery("select a from Address a", Address.class).getResultList();

    > 스칼라 타입 프로젝션 : 조회할 대상을 기본 데이터 타입으로 지정

      - 여러 값 조회 방법

        > Query 타입 조회 : 하나의 데이터 행을 Object[]로 리턴함

        > Object[] 조회 : 위 특성을 이용해서 ResultList의 제너릭을 Object[]로 지정

        > new 명령어로 조회

          - 엔티티가 아니면서 생성자를 갖는 새로운 클래스(DTO)를 만들어서 리턴

          - 순서와 타입이 일치하는 생성자 필요

//MemberDTO는 엔티티가 아닌 클래스이다. new 연산자와 함께 패키지 이름을 명시해주어야 한다.
List<MemberDTO> resultList = em.createQuery(
	"select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class);

  - 페이징 API

    > JPA는 페이징을 다음 두 API로 추상화

      - setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)

      - setMaxResults(int maxResult) : 조회할 데이터 수

    > 예제

//age 내림차순 0번 째 값 부터 10개 가져오는 방식
em.createQuery("select m from Member m order by m.age desc", Member.class)
	.setFirstResult(0)
    .setMaxResults(10)
    .getResultList();

      - 세부 쿼리는 DB에 따라 다르게 설정됨(방언)

        > 예) Oracle DB는 1부터 시작하고 MySql은 0부터 시작하는 차이가 있는데, JPA는 설정된 방언에 맞게 알아서 쿼리를 생성해 준다

  - 조인

    > 내부 조인 : select m from Member m [INNER] JOIN m.team t

    > 외부 조인 : select m from Member m LEFT [OUTER] JOIN m.team t

      - 조인 대상 필터링

        > select m, t FROM Member m LEFT JOIN m.team t on t.name ='A'

    > 세타 조인(연관관계가 없는 엔티티 간의 조인) : select count(m) from Member m, Team t where m.username = t.name

    > ON 절 

      - 연관관계 없는 엔티티 외부 조인 가능

      - 예) 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

  - 서브쿼리

    > 특징과 제약

      - JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능

      - HIBERNATE에서만 SELECT 절 가능

      - FROM절에서의 서브쿼리는 불가능 -> 정 안되겠다 싶은 것들은 app쪽에서 가공

    > 지원 함수

      - [NOT] EXISTS (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)

      - [NOT] IN (subquery) : 하나라도 같은 것이 있으면 참  

    > 예) 나이가 평균보다 많은 회원

      - 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

1.4. 타입 표현

  > 문자 : 'HELLO', 'She''s'(작은따옴표 사용)

  > 숫자 : 10L(Long), 10D(Double), 10F(Float)

  > BOOLEAN : TRUE, FALSE

  > ENUM : jpabook.MemberType.Admin(패키지명 까지 포함)

  > 엔티티 타입 : TYPE(m) = Member

    - select i from Item i where type(i) = Book

    - 엔티티의 타입을 체크해줄 수 있다.

1.5. 조건식

  > 기본 CASE 식 - QueryDSL에서 더 쉽게 가능

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

  > 단순 CASE 식 - 기본 CASE식에서 조건만 ==으로 변경

    - (... when m.age <= 10 ...) -> (... when 10 ...)

  > COALESCE - 하나 씩 조회해서 null이면 대체값 사용(nvl과 유사)

    - select coalesce(m.username, '이름 없는 회원') from Member m

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

    - select NULLIF(m.username, '관리자') from Member m -> 관리자는 null 리턴

1.6. 기본 함수

  > JPQL 표준 함수 - 모든 DB 가능

    - CONCAT : select concat('a', 'b') from Member m

    - SUBSTRING : select substring(m.username, 2, 3) from Member m

    - TRIM

    - LOWER, UPPER

    - LENGTH

    - LOCATE : select locate('de', 'abcdefg') from Member m

    - ABS, SQRT, MOD

    - SIZE, INDEX(JPA 전용) : select size(t.members) From Team t

  > 사용자 정의 함수

    - 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.

package dialect;
/** 방언 클래스를 상속받은 클래스 */
public class MyH2Dialect extends H2Dialect {
	public MyH2Dialect (){
    	registerFunction("함수이름", new StandardSQLFunction("함수이름", StandardBasicTypes.STRING));
    }
}
<!-- persistence.xml -->
<persistence ... > <persistence-unit name="이름"> <properties>
	...
    <!-- 방언 클래스를 상속받은 클래스를 value로 설정 -->
	<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
    ...
</properties> </persistence-unit> </persistence>

  > DB종속 함수

    - DBDialect 클래스에서 확인 가능

 

1.7. 한계

  - qlString은 문자열이기 때문에 동적쿼리를 만들기 어렵다.

    > 파라미터 바인딩으로 해결 가능

 

2. Criteria

2.1. 용도와 특징

  - 용도 : 동적 쿼리를 명확하게 만들고자 할 때

  - 장점 : 문자가 아닌 자바코드로 JPQL을 작성할 수 있음

  - 단점 : SQL스럽지 않고 알아보기 쉽지 않음 -> 그래서 잘 안씀

  - Criteria 대신에 QueryDSL 사용 권장

 

3. QueryDSL

3.1. 특징

  - 세팅이 복잡함

  - 문자가 아닌 자바코드로 JPQL을 작성할 수 있음

  - 컴파일 시점에 문법 오류를 찾을 수 있음

  - 동적 쿼리를 만들기 쉬움

  - www.querydsl.com 

  - JPQL과 거의 1:1

 

4. 네이티브 SQL

4.1. 특징 

  - JPQL만으로 해결할 수 없는 특정 DB에 의존적인 기능을 사용하고자 할 때 활용

  - em.createNativeQuery("select * from MEMBER");

 

 

Comments