Spring Boot & Rest API (3)

Spring Boot & Rest API (3) - JPA 조건

이전글

JPA 조건 적용 방법

JPA 에서 자동생성된 CRUD 를 이용했지만 일반적인 상황에선 좀더 많은 조건들이 쿼리에 들어가게 된다.

다양한 조건을 이용하여 JPA 에서 데이터를 가져오는 방법을 확인해 보자

1. Query Creation 이용

JPA 공식 문서에 가장 처음에 나와있는 방법이다.
JPA 에서 공식적으로 제공되는 키워드들이 있는데 이를 이용하여 repository 인터페이스에 추가만 하는 방법이다.

(ProductController.java)
...
    @GetMapping(value = "/menu/{count}", produces = "application/json; charset=utf-8")
    public CoffeeMenuList getMenu(
    @PathVariable(value = "count") int count,
    @RequestParam(value = "type", defaultValue = "caffeine") String caffeineType,
    @RequestParam(value = "price", required = true) int price) {
        List<CoffeeMenu> list = coffeeRepository.findByPriceGreaterThanEqual(price);

        return new CoffeeMenuList(list);
    }
...
(CoffeeRepository.java)
@Repository
public interface CoffeeRepository extends JpaRepository<CoffeeMenu, String> {
        List<CoffeeMenu> findByPriceGreaterThanEqual(int price);
}

이렇게 작성을하면 Spring 에서 자동으로 쿼리를 만들어 주어 조건을 추가할 수 있다.

기존 방법과 마찬가지로 자동생성되어 개발자가 크게 신경쓰지 않아도 된다는 장점이 있지만 조건이 많을수록 복잡하고 알아보기 어렵다는 단점이 있다.

2. Query Annotation

다음은 @Query 어노테이션을 이용하여 직접 쿼리를 생성하는 방법이다.

(ProductController.java)
...
    @GetMapping(value = "/menu_q/{count}", produces = "application/json; charset=utf-8")
    public CoffeeMenuList getMenuByQuery(
    @PathVariable(value = "count") int count,
    @RequestParam(value = "type", defaultValue = "caffeine") String caffeineType,
    @RequestParam(value = "price", required = true) int price) {
        List<CoffeeMenu> list = coffeeRepository.findByPriceQuery(price);

        return new CoffeeMenuList(list);
    }
...
(CoffeeRepository.java)
@Repository
public interface CoffeeRepository extends JpaRepository<CoffeeMenu, String> {
        @Query("SELECT c FROM CoffeeMenu c WHERE c.price > :price")
        List<CoffeeMenu> findByPriceQuery(@Param("price") int price);
}

이렇게 @Query 어노테이션을 이용하여 JPQL 을 작성하면 좀더 보기 쉬운 방법으로 쿼리 작성이 가능하다. 다만 쿼리를 직접 적기때문에 자동생성을 제대로 이용하기 어렵다는 단점이 있다.
즉, 변화에 민감한 코드가 되어버릴 수 있다.

3. Specification

Specification 관련 인터페이스와 메소드를 추가하는 방법이다.

(ProductController.java)
...
    @GetMapping(value = "/menu_s/{count}", produces = "application/json; charset=utf-8")
    public CoffeeMenuList getMenuBySpec(
    @PathVariable(value = "count") int count,
    @RequestParam(value = "type", defaultValue = "caffeine") String caffeineType,
    @RequestParam(value = "price", required = true) int price) {
        List<CoffeeMenu> list = (List<CoffeeMenu>) coffeeRepository.findAll(CoffeeSpecification.gtPrice(price));


        return new CoffeeMenuList(list);
    }
...
(CoffeeRepository.java)
@Repository
public interface CoffeeRepository extends JpaRepository<CoffeeMenu, String>,
    JpaSpecificationExecutor<CoffeeMenu> {
}
(CoffeeSpecification.java)
package com.example3.demo.specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.example3.demo.entity.CoffeeMenu;

import org.springframework.data.jpa.domain.Specification;

final public class CoffeeSpecification {
    private CoffeeSpecification() {
    }

    public static Specification<CoffeeMenu> gtPrice(int price) {
        return new Specification<CoffeeMenu>() {

            @Override
            public Predicate toPredicate(Root<CoffeeMenu> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
                return cb.gt(root.get("price"), price);
            }

        };
    }

}

이경우 CoffeeSpecification 이라는 클래스의 스태틱 메소드를 이용한다.
toPredicate() 함수를 구현하여 자유롭게 작성이 가능하며 JPQL 형태가 아닌 소스코드 형태로 다룰 수 있다는 장점이 있다.

정리

간단한 조건문을 이용하여 JPA 와 조건문을 이용하는 방법을 사용해 보았다.
사실 다양한 버전의 클래스들이 존재하여 이보다 더 많은 방법으로 조건을 다룰 수 있었지만 가장 최신 JPA docs 와 가장 대표적인 Specification 두가지를 참고하여 작성했다.
위 방법들은 세가지 모두 섞어 사용해도 잘 작동한 것으로 확인했으므로 상황에따라, 혹은 기호에따라 적절한 방법을 사용하기 바란다.

엮인글

Spring Boot & 테스트 코드 작성 (1) - JUnit
Spring Boot & 테스트 코드 작성 (2) - Mock, Mockito
Spring Boot & 테스트 코드 작성 (3) - MockMvc
Spring Boot & Rest API (1)
Spring Boot & Rest API (2) - JPA, MySQL
Spring Boot & Rest API (3) - JPA 조건



참조

JPA Docs, 5.3 Query Methods
Spring Data JPA의 Specifcation을 이용한 검색 조건 조합의 편리함
JPA - Criteria Query(객체지향 쿼리 빌더), JPQL Query Builder Class
CriteriaQuery 사용하기
JPA Criteria & Specification
[Spring JPA] JPQL
JPA 사용법 (JpaRepository)
[JPA] Specification - 조건문 추가하기

+ Recent posts