JPA를 사용할 때 입력 값에 따라 동적으로 검색 조건을 변경하려면 Criteria API를 사용하면 되지만, 이게 참 이쁘지가 않고 코드를 만지다 보면 짜증이 나기도 한다. 최근에 하는 프로젝트에서 Spring Data JPA를 선택했는데, Spring Data JPA 덕분에 검색 조건을 조합할 때 발생하는 짜증을 많이 감소시킬 수 있게 되었다. Spring Data JPA가 Repository 또는 Dao의 인터페이스만 정의하면 알아서 구현체를 만들어준다는 장점이 정말 좋은데, 그에 못지 않게 검색 조건을 추상화한 Specification을 지원한다는 것 역시 아주 마음에 든다.

Specification을 사용하는 방법은 아주 간단한데, 그 방법은 다음과 같다.
  1. Specification을 입력 받도록 Repository 인터페이스를 정의하기
  2. 검색 조건을 모아 놓은 클래스 만들기
  3. 검색 조건을 조합한 Specification 인스턴스를 이용해서 검색하기
하나씩 만들어보자.

1. Specification을 입력 받는 Repository 메서드 정의
Spring Data에서 Specification은 검색 조건을 추상화하기 위해 사용되는데, 이 추상화된 검색 조건을 이용해서 데이터를 검색하도록 구현하려면 다음과 같이 Specification을 검색 메서드의 파라미터로 추가해주기만 하면 된다.

public interface ContentRepository extends Repository<Content, Long> {

Page<Content> findAll(Specification<Content> spec, Pageable pageable);

}


2. 검색 조건 모아 놓은 클래스 만들기
다음에 할 일은 검색 조건을 모아 놓은 클래스를 하나 만드는 것이다. 이 클래스를 구현할 때 JPA의 Criteria API를 사용하게 된다. 아래는 구현 예이다.

public class ContentSpecs {


    public static Specification<Content> titleLike(final String keyword) {

        return new Specification<Content>() {

            @Override

            public Predicate toPredicate(Root<Content> root,

                    CriteriaQuery<?> query, CriteriaBuilder cb) {

                return cb.like(root.get(Content_.title), "%" + keyword + "%");

            }

        };

    }


    public static Specification<Content> category(final Category category) {

        return new Specification<Content>() {

            @Override

            public Predicate toPredicate(Root<Content> root,

                    CriteriaQuery<?> query, CriteriaBuilder cb) {

                return cb.equal(root.get(Content_.category), category);

            }

        };

    }

    ... // 기타 검색 조건 생성 클래스

}


위에서 눈여겨 볼 점은 Criteria를 이용해서 검색 조건을 지정하는 코드가 category(), titleLike()와 같은 메서드로 추상화된다는 점이다.

3. 검색 조건 조합해서 검색하기
이제 할 일은 앞서 만든 검색 조건을 모아 놓은 클래스를 이용해서 검색 조건을 조합하고 Repository를 이용해서 결과를 가져오는 것이다. 아래는 예시 코드이다.

Specifications<Content> spec = Specifications.where(ContentSpecs.titleLike(query));
if (category != null) {
    spec.and(ContentSpecs.category(category));
}
Page<Content> p = contentRepository.findAll(pageRequest);


Spring Data JPA가 제공하는 Specifications를 사용하면 두 개 이상의 Specification을 AND나 OR 등으로 조합할 수 있다.


왜 좋지?
이 방식이 마음에 드는 이유는 검색 조건을 생성할 때 도메인의 용어를 사용할 수 있게 된다는 점이다. 앞서 만들었던 ContentSpecs.titleLike()만 보더라도 이 검색 조건이 제목을 LIKE 검색하기 위한 조건이라는 것을 알 수 있다.

마음에 드는 또 다른 이유는 검색 조건을 조합하는 코드에서 Criteria와 같은 실제 구현 기술의 타입을 사용하지 않는다는 점이다. 사용자는 ContentSpecs.category() 메서드를 사용해서 검색 조건을 추상화한 Specification 객체를 구하기만 하면 된다. 내부적으로 JPA의 Criteria를 사용하는지의 여부는 전혀 알 필요가 없다.

마지막으로 이 방식은 누구나 쉽게 만들 수 있을 만큼 쉽다. 처음 이 방식을 접하는 경력자 뿐만 아니라 이제 3개월 된 신입들도 별다른 어려움 없이 위 방식에 적응했다.

불필요한 코드 작성을 줄이고 그러면서도 이전보다 쉽게 DB 연동 부분을 처리하고 싶은 개발자분들에게 JPA + Spring Data JPA/Specification의 조합을 아주 강력하게 권장한다. 


출처 - http://javacan.tistory.com/entry/SpringDataJPA-Specifcation-Usage


Posted by linuxism
,