티스토리 뷰

Back-end

[JPA]JPA Criteria & Specification

코헴 2019. 1. 15. 01:19


JPA Criteria & Specification

Criteria API는 JPA 2.0에 추가되었고, 다음과 같은 구문을 지원한다. (UPDATE 및 DELETE 기능은 JPA 2.1부터 지원한다.)

  • SELECT

  • FROM

  • WHERE

  • GROUP BY

  • ORDER BY


JPA Criteria 는 동적쿼리를 사용하기위한 JPA 라이브러리이다. 기본적으로 JPQL(JPA Query Language)과 같이 엔티티 조회를 기본으로하며, 컴파일 시점에서 에러를 확인할수 있는 특징을 가진다.


예를들면 다음과 같다.

TypedQuery<Order> query = em.createQuery("SELECT o FROM Order", Order.class);
List<Order> resultList = query.getResultList();

위 예제에서는 컴파일은 정상 동작 하지만, 실행시에는 에러가 발생한다. 쿼리문에 오류가 존재하기 때문이다.

Criteria에서는 위와같은 상황에서 컴파일단에서 에러를 확인할수 있다.



CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Order> criteriaQuery = criteriaBuilder.createQuery(Order.class);
Root<Order> order = criteriaQuery.from(Order.class);
criteriaQuery.select(order);

TypedQuery<Order> typedQuery = em.createQuery(criteriaQuery);
List<Order> resultList = typedQuery.getResultList();

위 예제에서 알 수 있듯이, 문자열로 정의하는 JPQL과 달리 Criteria는 각 쿼리 요소를 나타내는 자바 객체의 인스턴스로 정의된다.

조금더 복잡한 예시를 살펴보자.



    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

CriteriaQuery<Board> criteriaQuery = criteriaBuilder.createQuery(Board.class);

Root<Board> root = criteriaQuery.from(Board.class);

Predicate restrictions = criteriaBuilder.equal(root.get("nickName"), "코헨");

criteriaQuery.where(restrictions);

criteriaQuery.orderBy(criteriaBuilder.desc(root.get("boardIdx")));

TypedQuery<Board> boardListQuery = entityManager.createQuery(criteriaQuery).setFirstResult(startRow).setMaxResults(pageSize);

List<Board> boardList = boardListQuery.getResultList();

return boardList;

라인별로 구문을 해석해보면 다음과 같다.


CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();

// CriteriaBuilder 인스턴스를 생성


CriteriaQuery<Board> criteriaQuery = criteriaBuilder.createQuery(Board.class);

// Board 제너릭 형식으로 CriteriaQuery 인스턴스를 생성한다. 이를통해 Typed-Safe한 객체를 생성가능.


Root<Board> root = criteriaQuery.from(Board.class);

// Root는 영속적 엔티티를 표시하는 쿼리표현식과 같으며, SQL 의 FROM 이라고 생각하면 된다.


Predicate restrictions = criteriaBuilder.equal(root.get("nickName"), "코헨");

criteriaQuery.where(restrictions);


// Predicate의 경우 SQL의 WHERE절과 같다. criteriaBuilder로부터 생성된다.

criteriaQuery.orderBy(criteriaBuilder.desc(root.get("boardIdx")));


// ORDER BY 절의 경우도 위와같이 사용 가능하다.

TypedQuery<Board> boardListQuery = entityManager.createQuery(criteriaQuery).setFirstResult(startRow).setMaxResults(pageSize);


List<Board> boardList = boardListQuery.getResultList();

//CreateQuert 인스턴스로부터 TypedQuery를 생성하고, 해당 TypedQuery로부터 실행결과를 객체에 담음으로써 질의결과를 매핑한다.



JPQL이 단순하고 읽기 쉽다는 이점이 있는 반면, Criteria는 메타모델을 통한 객체의 인스턴스로 사용하므로 Type-Safe한 장점을 갖고있다. 또한 Criteria의 경우 변환되는 JPQL을 캐싱하여 성능을 향상시킬 수 있다고한다. (이는 추후에 더 학습!)




Specification

Specification는 검색조건을 추상화한 객체다. 그말은 즉, 검색 조건에 대해 Specification에 생성하고, 이를 통해 다양한 조건의 검색을 할 수 있다는 뜻이다.

Spring Data JPA에서 Repository 또는 Dao의 인터페이스만 정의하면 알아서 해당 구현체를 만들어주며, 해당 구현체에서는 Specification을 지원하기때문에 구현한 Specification 객체를 파라미터에 넣기만 해주면, 해당 조건에 부합되는 객체를 결과값으로 조회가능하다.

Specification 구현체

  • 클래스 파일을 확인하면 내부적으로는 criteria api를 의존하고있음을 알수있다.



Specification 다음과 같은 방식으로 조건 검색을 한다.

  1. Specification을 입력 받도록 Repository 인터페이스를 정의하기

  2. 검색 조건을 모아 놓은 클래스 만들기 (Specifications 객체)

  3. 검색 조건을 조합한 Specification 인스턴스를 이용해서 검색하기



아래는 실제 Specification 객체를 활용하여 만든 Specification 인스턴스

public static Specification<SystemUseHistory> searchSystemUseHistory(Map<String, Object> filter) {
   return (root, query, cb) -> {
       List<Predicate> predicates = new ArrayList<>();

       query.orderBy(cb.desc(root.get("createdAt")));

       filter.forEach((key, value) -> {
           String likeValue = "%" + value + "%";

           switch (key) {
               case "gameName":
                   Predicate gameNamePredicate = cb.like(root.get("game").get("name").as(String.class), likeValue);
                   predicates.add(gameNamePredicate);
                   break;
               case "companyName":
                   Predicate companyNamePredicate = cb.like(root.get("member").get("companyInfo").get("name").as(String.class), likeValue);
                   predicates.add(companyNamePredicate);
                   break;
               case "startDate":
                   Predicate startDatePredicate = cb.greaterThanOrEqualTo(root.get("crSeatedAt"), (Date) value);
                   predicates.add(startDatePredicate);
                   break;
               case "endDate":
                   Predicate endDatePredicate = cb.lessThanOrEqualTo(root.get("createdAt"), (Date) value);
                   predicates.add(endDatePredicate);
                   break;
               case "memberName":
                   Predicate memberNamePredicate = cb.like(root.get("member").get("name").as(String.class), likeValue);
                   predicates.add(memberNamePredicate);
                   break;
               case "uri":
                   Predicate uriPredicate = cb.like(root.get("uri").as(String.class), likeValue);
                   predicates.add(uriPredicate);
                   break;
          }
      });

       return cb.and(predicates.toArray(new Predicate[0]));
  };
}



이를 활용하려면 단지 JpaSpecificationExecutor를 상속받은 Repository 메소드의 파라미터를 추가해주기만 하면 된다.

systemUseHistoryRepository.findAll(HistorySpecification.searchSystemUseHistory()); // 끝!


'Back-end' 카테고리의 다른 글

[Back-end]Procedure? StoredProcedure  (0) 2019.07.30
[Spring] Servlet이란?  (0) 2019.04.09
[Back-end] 메이븐이란?  (0) 2019.01.29
[SpringFramework]Application.java 에 대하여  (0) 2019.01.28
[Back-end] jwt token 통신과정  (0) 2019.01.14
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함