DB/JPA

[JPA] JPQL 간단 정리

꾸준함. 2021. 10. 16. 18:12

개요

JPA는 아래와 같이 다양한 쿼리 방법을 지원합니다.

  • JPQL
  • JPA Criteria
  • QueryDSL
  • Native SQL
  • JDBC API 직접 적용

이번 게시글에서는 간단하게 위 쿼리 방법들을 설명한 후 JPQL 기본 문법과 기능에 대해 알아보겠습니다.

 

1. 다양한 쿼리 방법 소개

 

1.1 JPQL

  • JPA가 테이블이 아닌 엔티티 객체를 중심으로 개발하는데, JPQL 역시 엔티티 객체를 대상으로 검색하는 쿼리 방법 (객체지향적인 것이 핵심)
  • JPQL은 SQL을 추상화한 객체 지향 쿼리 언어
    • 실제로 SQL 문법과 유사하여 ANSI 표준 키워드 전부 지원 (SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN)
    • 앞서 언급한 대로 JPQL은 엔티티 객체를 대상으로 쿼리
    • 반면, SQL은 DB 테이블을 대상으로 쿼리
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 실질적으로 불가능
    • 따라서, 애플리케이션이 필요한 최소한의 데이터만 불러와야 함

 

1.2 Criteria

  • JPQL로 Dynamic Query 작성하기 어렵기 때문에 등장한 개념
  • 장점
    • 문자가 아닌 자바 코드로 JPQL을 작성하므로 컴파일 시점에서 에러를 잡아줌
  • 단점
    • 아래 예시를 보면 알 수 있다시피 SQL스럽지 않고 복잡하며 실용성이 없음
  • Criteria 대신 QueryDSL을 통해 동적 쿼리 작성하는 것을 추천

 

 

 

1.3 QueryDSL

  • Criteria와 마찬가지로 문자가 아닌 자바코드로 JPQL을 작성하여 컴파일 단계에서 에러를 잡을 수 있음
  • 동적 쿼리를 작성하기 쉬운 쿼리 언어
  • 단순하고 난이도가 상대적으로 쉽기 때문에 실무 사용 권장
  • 이후 별도로 공부하고 정리할 예정

 

1.4 Native SQL

  • SQL을 직접 작성해서 사용하는 기능
  • JPQL로 해결할 수 없는 특정 DB에 의존적인 기능
    • ex) ORACLE의 CONNECT BY와 같이 특정 DB만 사용하는 SQL 힌트
  • 컴파일 단계에서 에러를 잡을 수 없으므로 에러에 취약함

 

 

 

1.5 JDBC API 직접 적용

  • JDBC 커넥션을 직접 사용하거나 스프링 JdbcTemplate, MyBatis 등을 함께 사용
    • 단, 위와 같은 기능은 JPA와 연관이 없으므로 PersistenceContext를 강제로 플러쉬 해야 함
    • 앞서 정리한 게시글 https://jaimemin.tistory.com/1898을 보면 데이터는 persist 메서드가 아닌 commit 메서드를 호출해야 DB에 들어감 (persist를 할 경우 영속성 컨텍스트에서 관리하지만 이는 JPA를 사용할 때만 접근 가능) 
    • 따라서, JPA와 연관 없는 쿼리를 호출할 때는 수동으로 flush를 진행해서 데이터를 실제 DB에 넣어줘야 오류 방지 가능

 

2. JPQL 기본 문법과 기능

 

2.1 JPQL 문법

  • JPQL은 SQL을 추상화한 객체지향 언어이므로 기본적인 문법은 비슷
    • ex) SELECT e FROM Employee AS e WHERE e.age > 30
  • 엔티티와 속성은 위 예시처럼 대소문자 구분
    • 엔티티: Employee
    • 속성: age
  • JPQL 키워드는 대소문자 구분을 하지 않지만 키워드는 대문자로 하는 것이 가독성 측면에서 유리
  • 엔티티 중심으로 검색하므로 테이블명이 아닌 엔티티명을 사용
    • @Entity(name = "XXX")라고 가정하면 From XXX
  • 위 예시처럼 Alias는 필수 (Employee AS e)
    • AS는 생략 가능
  • 집합과 정렬을 위한 COUNT, SUM, AVG, MAX, MIN, GROUP BY, HAVING 그리고 ORDER BY 키워드 모두 지원

 

2.2 TypeQuery vs Query

  • TypeQuery는 반환 타입이 명확할 때 사용
  • Query의 경우 반환 타입이 불명확할 경우 사용

 

 

 

2.3 getResultList() vs getSingleResult()

  • 두 메서드 모두 2,2에서 소개한 TypeQuery 혹은 Query의 메서드
  • 결과가 하나 이상일 때는 리스트 형태로 반환하는 getResultList() 메서드 사용
    • 결과가 없을 경우 빈 리스트 반환하므로 exception 발생 X
  • 결과가 정확히 하나일 경우에는 단일 객체를 반환하는 getSingleResult() 메서드 사용
    • 주의할 점은, 결과가 없을 경우나 결과가 둘 이상일 때 exception이 발생한다는 점
    • 결과가 없을 경우 -> NoResultException 예외 발생
    • 결과가 둘 이상일 경우 -> NonUniqueResultException 발생
    • Hibernate의 경우 위와 같은 NoResultException 예외가 발생할 경우 catch 해서 결과가 없더라도 예외가 발생하지 않도록 처리
  • 안전하게 처리하기 위해서는 getResultList() 메서드를 사용하는 것을 추천

 

2.4 파라미터 바인딩

  • 이름 기준 혹은 위치 기준으로 파라미터를 바인딩해주는 기능
  • 위치는 수시로 바뀔 수 있으므로 확실한 이름 기준으로 바인딩해주는 것을 추천
  • 동적 쿼리를 작성할 때 사용하는 기능

 

 

 

 

2.5 프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라고 함
  • 프로젝션은 크게 3가지로 나눌 수 있음
    • 엔티티 프로젝션
    • 임베디드 타입 프로젝션
    • 스칼라 타입 프로젝션

 

2.5.1 엔티티 프로젝션

  • 이름에서도 유추할 수 있다시피 엔티티를 반환받는 SELECT 쿼리문
  • 엔티티 프로젝션 시 조회된 모든 엔티티들은 PersistenceContext로 인해 관리가 됨
  • PersistenceContext에 대한 자세한 설명은 https://jaimemin.tistory.com/1898 참고

 

2.5.2 임베디드 타입 프로젝션

  • 앞서 https://jaimemin.tistory.com/1953에서 정리한 임베디드 값 타입을 조회하는 SELECT 쿼리문
  • 아래 예시처럼 주소를 조회할 때 유용하게 쓰임

 

2.5.3 스칼라 타입 프로젝션

  • 여러 타입의 값을 조회할 때 사용하는 SELECT 쿼리문
  • 타입이 여러 개이므로 Object 배열을 반환받는 방법이 있고
  • 혹은 스칼라 타입 프로젝션을 위한 별도 DTO를 생성하여 new 명령어를 통해 조회하는 방법이 있음
    • Object 배열로 반환받는 방법보다 가독성 좋음
    • 단, 패키지명을 포함한 전체 클래스명을 입력해야 하는 불편함이 존재하며 순서와 타입이 일치하는 생성자가 필요하다는 단점도 있음 => QueryDSL 사용 시 해결 가능


 

2.6 페이징 API

  • JPA는 페이징을 아래의 두 API로 추상화
    • setFirstResult(int startPosition): 조회 시작 위치
    • setMaxResults(int maxResult): 조회할 데이터 수
  • ORACLE의 페이징 처리에 비해 훨씬 간단함
  • MySQL 혹은 PostgreSQL의 offset, limit과 유사


 

2.7 Join

  • INNER JOIN, OUTER JOIN, CROSS JOIN 모두 지원
  • JPA 2.1부터 아래의 ON 절을 활용한 JOIN 또한 지원
    • 조인 대상 필터링
    • 연관관계없는 엔티티 OUTER JOIN (Hibernate 5.1 ~)


 

2.8 SubQuery

  • JPA 또한 SQL처럼 서브 쿼리를 지원하며 아래의 키워드를 제공
    • [NOT] EXISTS (subquery)
    • {ALL | ANY | SOME} (subquery)
    • [NOT] IN (subquery)
  • 하지만, SQL과 달리 JPQL은 서브 쿼리 한계가 존재
    • JPA는 WHERE, HAVING 절에서만 서브 쿼리를 사용 가능
    • Hibernate를 이용할 경우 SELECT 절에서도 서브 쿼리 사용 가능
    • 하지만, FROM 절의 서브 쿼리는 현재 JPQL에서 사용 불가능
      • Native Query를 사용하거나
      • 쿼리를 두 번 호출하거나
      • 혹은 쿼리를 호출한 뒤 Application 단에서 제어를 하는 방식으로 우회해야 함

 

2.9 JPQL Type

  • 엔티티를 제외하고도 문자, 숫자, Boolean 타입 지원
  • Enum 타입도 지원하지만 이를 위해서는 스칼라 타입 프로젝션처럼 전체 패키지명을 명시해야 하는 불편한 점이 있음
    • 파라미터 바인딩을 통해 위 문제점을 해결 가능
  • 엔티티 타입도 지원하는데 이는 @Discriminator 어노테이션을 통해 명시한 DTYPE을 의미

 

2.10 사용자 정의 함수

  • 각 DB 방언(Dialect)마다 사용하는 함수가 다름
  • 따라서 특정 함수를 사용하기 위해서는 사용하는 DB 방언을 상속받은 뒤, 아래와 같이 사용자 정의 함수를 등록해야 함
  • 아래의 예시는 H2Dialect에 group_concat을 추가한 예시
    • CustomH2Dialect 클래스를 선언하고 H2Dialect를 상속받은 뒤 group_concat 함수를 등록
    • persistence.xml에 CustomH2Dialect 등록
    • 실제로 group_concat 함수를 호출

 

CustomH2Dialect

 

public class CustomH2Dialect extends H2Dialect {

    public CustomH2Dialect() {
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}

 

persistence.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="com.tistory.jaimemin.dialect.CustomH2Dialect"/>
            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

 

group_concat 함수 호출하는 에시


 

출처

자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한 강사님)

반응형

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

[JPA] 준영속(Detached) 상태 엔티티 수정하는 방법  (0) 2023.05.07
[JPA] JPQL 추가 정리  (0) 2021.10.18
[JPA] 값 타입 정리  (0) 2021.09.29
[JPA] 프록시와 연관관계 관리 정리  (0) 2021.09.14
[JPA] @MappedSuperclass  (0) 2021.09.07