1. 소개
Spring Data JPA는 쿼리 메서드 및 사용자 지정 JPQL 쿼리 를 포함하여 엔터티를 처리하는 다양한 방법을 제공합니다 . 그러나 때로는 Criteria API 또는 QueryDSL 과 같은 보다 프로그래밍적인 접근 방식이 필요합니다 .
Criteria API는 구문 오류를 방지하는 데 도움이 되는 입력된 쿼리를 생성하는 프로그래밍 방식을 제공합니다. 또한 Metamodel API와 함께 사용할 때 올바른 필드 이름과 유형을 사용했는지 확인하기 위해 컴파일 타임 검사를 수행합니다.
그러나 단점이 있습니다. 상용구 코드로 부풀려진 장황한 논리를 작성해야 합니다.
이 사용방법(예제)에서는 기준 쿼리를 사용하여 사용자 지정 DAO 논리를 구현하는 방법을 배웁니다. 또한 Spring이 상용구 코드를 줄이는 데 어떻게 도움이 되는지 설명합니다.
2. 샘플 신청
예제의 단순함을 위해 여러 가지 방법으로 동일한 쿼리를 구현합니다. 즉, 저자 이름과 String 을 포함하는 제목으로 책을 찾습니다 .
다음은 Book 엔터티입니다.
@Entity
class Book {
@Id
Long id;
String title;
String author;
// getters and setters
}
간단하게 유지하기 위해 이 예제에서는 Metamodel API를 사용하지 않습니다.
3. @Repository 클래스
아시다시피 Spring 구성 요소 모델 에서 @Repository 빈 에 데이터 액세스 논리를 배치해야 합니다 . 물론 이 논리는 Criteria API와 같은 모든 구현을 사용할 수 있습니다.
이렇게 하려면 autowire할 수 있는 EntityManager 인스턴스만 필요합니다.
@Repository
class BookDao {
EntityManager em;
// constructor
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> book = cq.from(Book.class);
Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
cq.where(authorNamePredicate, titlePredicate);
TypedQuery<Book> query = em.createQuery(cq);
return query.getResultList();
}
}
위의 코드는 표준 Criteria API 워크플로를 따릅니다.
- 먼저 쿼리의 다른 부분을 만드는 데 사용할 수 있는 CriteriaBuilder 참조 를 얻습니다 .
- CriteriaBuilder 를 사용하여 쿼리에서 수행하려는 작업을 설명 하는 CriteriaQuery<Book> 을 만듭니다 . 또한 결과에서 행의 유형을 선언합니다.
- CriteriaQuery<Book> 을 사용 하여 쿼리의 시작점( Book 엔터티) 을 선언하고 나중에 사용할 수 있도록 book 변수에 저장합니다.
- 다음으로 CriteriaBuilder 를 사용하여 Book 엔터티 에 대한 술어를 만듭니다 . 이러한 술어는 아직 아무런 효과가 없습니다.
- CriteriaQuery 에 두 술어를 모두 적용합니다 . CriteriaQuery.where(Predicate…) 는 논리 및 . 이것이 이러한 술어를 쿼리에 연결하는 지점입니다.
- 그런 다음 CriteriaQuery 에서 TypedQuery<Book> 인스턴스를 만듭니다.
- 마지막으로 일치하는 모든 Book 엔터티를 반환합니다.
@Repository 로 DAO 클래스를 표시했기 때문에 Spring은 이 클래스에 대한 예외 변환을 가능하게 합니다.
4. 커스텀 메소드로 리포지토리 확장하기
자동 사용자 정의 쿼리 를 갖는 것은 강력한 Spring Data 기능입니다. 그러나 때로는 자동 쿼리 메서드로는 만들 수 없는 보다 정교한 논리가 필요합니다.
이전 섹션에서와 같이 별도의 DAO 클래스에서 이러한 쿼리를 구현할 수 있습니다.
또는 @Repository 인터페이스 에 사용자 지정 구현이 있는 메서드를 포함하려면 구성 가능한 저장소 를 사용할 수 있습니다 .
사용자 정의 인터페이스는 다음과 같습니다.
interface BookRepositoryCustom {
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title);
}
다음은 @Repository 인터페이스입니다.
interface BookRepository extends JpaRepository<Book, Long>, BookRepositoryCustom {}
또한 이전 DAO 클래스를 수정하여 BookRepositoryCustom 을 구현 하고 이름을 BookRepositoryImpl 로 변경해야 합니다 .
@Repository
class BookRepositoryImpl implements BookRepositoryCustom {
EntityManager em;
// constructor
@Override
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
// implementation
}
}
BookRepository 를 의존성으로 선언하면 Spring은 BookRepositoryImpl 을 찾아 사용자 지정 메서드를 호출할 때 사용합니다.
쿼리에서 사용할 술어를 선택하고 싶다고 가정해 보겠습니다. 예를 들어 저자와 제목으로 책을 찾지 않으려면 저자만 일치시키면 됩니다.
전달된 인수가 null 이 아닌 경우에만 술어를 적용하는 것과 같이 여러 가지 방법이 있습니다 .
@Override
List<Book> findBooksByAuthorNameAndTitle(String authorName, String title) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> book = cq.from(Book.class);
List<Predicate> predicates = new ArrayList<>();
if (authorName != null) {
predicates.add(cb.equal(book.get("author"), authorName));
}
if (title != null) {
predicates.add(cb.like(book.get("title"), "%" + title + "%"));
}
cq.where(predicates.toArray(new Predicate[0]));
return em.createQuery(cq).getResultList();
}
그러나 이 접근 방식 은 코드를 유지 관리하기 어렵게 만듭니다.
이러한 술어를 외부화하는 것이 실용적인 해결책이 될 것입니다. JPA 사양을 사용하면 정확히 그 이상을 수행할 수 있습니다.
5. JPA 사양 사용
Spring Data 는 단일 술어를 캡슐화하기 위해 org.springframework.data.jpa.domain.Specification 인터페이스를 도입했습니다.
interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
사양 인스턴스 를 생성하는 방법을 제공할 수 있습니다 .
static Specification<Book> hasAuthor(String author) {
return (book, cq, cb) -> cb.equal(book.get("author"), author);
}
static Specification<Book> titleContains(String title) {
return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
}
이를 사용하려면 org.springframework.data.jpa.repository.JpaSpecificationExecutor<T> 를 확장하기 위한 저장소가 필요합니다 .
interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {}
이 인터페이스 는 사양 작업을 위한 편리한 메서드를 선언합니다 . 예를 들어, 이제 다음 과 같은 한 줄로 지정된 저자가 있는 모든 Book 인스턴스를 찾을 수 있습니다.
bookRepository.findAll(hasAuthor(author));
안타깝게도 여러 사양 인수를 전달할 수 있는 메서드가 없습니다 . 오히려 org.springframework.data.jpa.domain.Specification 인터페이스에서 유틸리티 메서드를 얻습니다.
예를 들어 두 개의 사양 인스턴스를 논리 and 와 결합할 수 있습니다 .
bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));
위의 예에서 where() 는 사양 클래스의 정적 메서드입니다.
이렇게 하면 쿼리를 모듈식으로 만들 수 있습니다. 게다가 Spring이 우리에게 제공했기 때문에 Criteria API 상용구를 작성할 필요가 없었습니다.
더 이상 기준 상용구를 작성할 필요가 없다는 의미는 아닙니다. 이 접근 방식은 우리가 본 워크플로, 즉 제공된 조건을 충족하는 엔터티 선택만 처리할 수 있습니다.
쿼리에는 그룹화, 선택한 것과 다른 클래스 반환 또는 하위 쿼리를 포함하여 지원하지 않는 많은 구조가 있을 수 있습니다.
6. 결론
이 기사에서는 Spring 애플리케이션에서 기준 쿼리를 사용하는 세 가지 방법에 대해 설명했습니다.
- DAO 클래스를 만드는 것이 가장 간단하고 유연한 방법입니다.
- 자동 쿼리와의 원활한 통합을 위해 @Repository 인터페이스 확장
- 사양 인스턴스 에서 술어를 사용 하여 간단한 경우를 더 명확하고 덜 장황하게 만들기
평소와 같이 예제는 GitHub 에서 사용할 수 있습니다 .