JPA - JPQL과 Criteria 쿼리

2021. 11. 17. 16:21 Java 관련/JPA

 

  • JPQL

    JPQL(Java Persistence Query Language)은 JPA를 구현한 프레임워크에서 사용하는 언어입니다. JPA 구현 프레임워크에서는 JPQL을 SQL로 변환해 데이터베이스에 질의하게 됩니다. JPQL은 테이블을 대상으로 쿼리하지 않고 객체를 고려해 쿼리합니다. 이 때문에 JPQL은 데이터베이스 테이블에 직접적인 의존 관계를 맺고 있지 않습니다. JPQL은 SQL과 비슷한 구조로 구성 됐습니다.

    SELECT ... FROM ...
    [WHERE ...]
    [GROUP BY ... [HAVING ...]]
    [ORDER BY ...]

    이때 JQPL DELTE나 UPDATE는 다음과 같은 두개의 쿼리로 나타낼 수 있습니다.

    DELETE FROM ... [WHERE ...]
    UPDATE ... SET ... [WHERE ...]

    예를 들어 JPQL을 이용해 SELECT 문을 작성해 보겠습니다.

    SELECT m FROM User AS u where u.userid="happy"

    JPQL의 유의사항은 User AS u처럼 별칭을 필수적으로 지정돼야 한다는 것 입니다. 다만 AS는 생략 가능하기 때문에 다음과 같이 작성할 수 있습니다.

    SELECT m FROM User u where u.userid="happy"

    JPQL 문을 작성했다면 JPQL을 실행하도록 createQuery() 메소드를 이용해 쿼리 객체를 생성해야 합니다. 예를 들어 JPQL을 이용해 나라 목록을 조회하려면 createQuery() 실행한 JPQL과 반환할 엔티티 클래스 타입을 넘겨줍니다. 다음과 같은 형태로 JPQL을 실행해 사용할 수 있습니다.

      TypedQuery<Country> query =
          em.createQuery("SELECT c FROM Country c", Country.class);
      List<Country> results = query.getResultList();
      for(Country c: results){
        System.out.println("country = "+ c);
      }

    위에서는 TypedQuery<Country>를 이용했는데, 쿼리 객체를 생성하고 조회할 때 타입 변환이 필요 없어 편리합니다. 만약 위 위예제의 쿼리문에 에러가 있으면 동적 쿼리의 특성으로 인해 컴파일 시 에러가 발생합니다. JPQL 작성시 장점이 하나 있는데 파라미터 바인딩이라는 방법이 있습니다. 파라미터 바인딩은 JPQL문 안에 파라미터로 설정한 기준 파라미터 앞에 :를 설정합니다.

    SELECT p FROM Parent p WHERE p.name = :name

    그리고 setParameter 메서드를 이용해 외부에서 파라미터를 설정할 수 있습니다.

    query.setParameter("name", name);

    전체 코드는 다음과 같습니다.

    String name = "testName1";
    TypedQuery<Parent> query = em.createQuery("" +
            "SELECT p FROM Parent p WHERE p.name = :name", Parent.class);
    query.setParameter("name", name);
    List<Parent> list = query.getResultList();
    for(Parent p : list) {
        System.out.println(p.toString());
    }

     

     

    JPA의 질의 방법

    JPA에서 데이터베이스 질의를 조회하는 방법이 있는데 다음과 같은 방법이 존재합니다.

    • Criteria
    • QueryDSL
    • 네이티브 SQL

     

    Criteria를 이용한 질의

    Criteria는 자바 코드를 이용해 JPQL을 작성할 수 있게 도와주는 타입 세이프(type-safe)를 제공하는 쿼리입니다. 네이티브 SQL 쿼리를 대체해 HQL, JPQL을 자바 코드를 이용해 작성할 수 있게 해 줍니다. 하이버네이트는 javax.persistence.criteria.CriteriaQuery를 통해 Criteria 기능을 제공하고 있습니다.

     

    JPQL을 Criteria로 변환

    where 절은 where 메소드를 이용해 조회합니다. 예를 들어 다음과 같은 JPQL 쿼리가 있다고 해 보겠습니다.

    SELECT c FROM Country c WHERE c.population > :p

    위 JPQL 쿼리를 criteria query API를 이용해 작성해 보면 다음과 같이 작성할 수 있습니다.

      CriteriaQuery<Country> q = cb.createQuery(Country.class);
      Root<Country> c = q.from(Country.class);
      q.select(c);
      ParameterExpression<Integer> p = cb.parameter(Integer.class);
      q.where(cb.gt(c.get("population"), p));

    위에 작성된 부분에서 createQuery() 메소드에서 쿼리를 생성합니다.

    cb.createQuery(Country.class)

    createQuery() 메소드의 인자를 클래스 엔티티 대신 JPQL 문으로 변경해도 됩니다.

     

    예) 루트 엔티티 선택

    Criteria API를 이용하려면 CriteriaBuilder를 이용합니다. 다음은 CriteriaBuilder를 이용해 루트 엔티티를 선택하는 쿼리입니다.

    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    ​
    CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
    Root<Person> root = criteria.from( Person.class );
    criteria.select( root );
    criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
    ​
    List<Person> persons = entityManager.createQuery( criteria ).getResultList();

     

    예) 특정 컬럼만 선택

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery q = cb.createQuery(AuthorValue.class);
    Root root = q.from(Author.class);
    q.select(cb.construct(AuthorValue.class, root.get(Author_.firstName), root.get(Author_.lastName)));
    ​
    List authors = em.createQuery(q).getResultList();

     

    예) 여러 조건절을 추가

    EntityManager em = lazyEM.get();
        CriteriaBuilder cb = em.getCriteriaBuilder();
    ​
        final CriteriaQuery<User> query = cb.createQuery( User.class );
        final Root<User> u = query.from( User.class );
        u.fetch( User_.addresses );
    ​
        query.select( u ).distinct( true ).where( cb.equal( u.get( User_.firstName ), "Emmanuel" ) );
    ​
        final TypedQuery<User> typedQuery = em.createQuery( query );
        typedQuery.setFirstResult( 0 ).setMaxResults( 20 );
        final List<User> resultList = typedQuery.getResultList();
        return  resultList;

     

    예) Join 절을 추가

    EntityManager em = lazyEM.get();
        CriteriaBuilder cb = em.getCriteriaBuilder();
    ​
        final CriteriaQuery<User> query = cb.createQuery( User.class );
        final Root<User> u = query.from( User.class );
        final SetJoin<User,Address> a = u.join( User_.addresses );
        u.fetch( User_.addresses );
    ​
        query.select( u ).distinct( true ).where( cb.equal( a.get( Address_.city ), "Paris" ) );
    ​
        final TypedQuery<User> typedQuery = em.createQuery( query );
        typedQuery.setFirstResult( 0 ).setMaxResults( 20 );
        final List<User> resultList = typedQuery.getResultList();
        return  resultList;

     

    QueryDSL을 이용한 질의

    QueryDSL은 JPA 표준은 아니며 오픈 소스 프로젝트입니다. QueryDSL은 Criteria보다 코드가 단순하고 가독성이 좋은 장점이 있습니다. 타입 안전성(type safety)를 보장하는 것이 Querydsl의 가장 큰 원칙입니다. QueryDSL의 메이븐 설정은 다음과 같습니다.

    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-apt</artifactId>
      <version>${querydsl.version}</version>
      <scope>provided</scope>
    </dependency>    
        
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-jpa</artifactId>
      <version>${querydsl.version}</version>
    </dependency>
    ​
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.1</version>
    </dependency>   

     

    예) 조회 질의

    "bob"에 대한 질의

    QCustomer customer = QCustomer.customer;
    JPAQuery query = new JPAQuery(entityManager);
    Customer bob = query.from(customer)
      .where(customer.firstName.eq("Bob"))
      .uniqueResult(customer);

     

    예) 정렬 질의

    QCustomer customer = QCustomer.customer;
    query.from(customer)
        .orderBy(customer.lastName.asc(), customer.firstName.desc())
        .list(customer);

     

    예) 조인 질의

    다음과 같은 JPQL 쿼리가 있다고 하겠습니다.

    from Cat as cat
        inner join cat.mate as mate
        left outer join cat.kittens as kitten

    위와 동일한 Quertydsl은 다음과 같이 작성합니다.

    QCat cat = QCat.cat;
    QCat mate = new QCat("mate");
    QCate kitten = new QCat("kitten");
    query.from(cat)
        .innerJoin(cat.mate, mate)
        .leftJoin(cat.kittens, kitten)
        .list(cat);

     

    네이티브 SQL

    네이티브 SQL을 이용한 파라미터 바인딩은 다음과 같이 작성합니다.

    Query q = em.createNativeQuery("SELECT a.firstname, a.lastname FROM Author a WHERE a.id = ?");
    q.setParameter(1, 1);
    Object[] author = (Object[]) q.getSingleResult();
     
    System.out.println("Author "
            + author[0]
            + " "
    + author[1]);

     

    참고



출처: https://happygrammer.tistory.com/149?category=891896 [happygrammer]