이 장에서는 Query by Example를 소개하고 사용 방법을 설명합니다.
QBE(Query by example)는 간단한 인터페이스를 갖춘 사용자 친화적인 쿼리 기술입니다. 동적 쿼리 생성이 가능하며 필드 이름이 포함된 쿼리를 작성할 필요가 없습니다. 실제로 Query by example에서는 store별 쿼리 언어를 사용하여 쿼리를 작성할 필요가 전혀 없습니다.
[Note]
이 장에서는 Query By Example의 핵심 개념을 설명합니다. 정보는 Spring Data Commons 모듈에서 가져옵니다. 데이터베이스에 따라 문자열 일치 지원이 제한될 수 있습니다.
QBE API는 네 부분으로 구성됩니다.
Probe: 채워진 필드가 있는 도메인 객체의 실제 예입니다.
ExampleMatcher
: ExampleMatcher
는 특정 필드를 일치시키는 방법에 대한 세부 정보를 전달합니다. 여러 예제에서 재사용할 수 있습니다.
Example
: Example
는 프로브와 ExampleMatcher
로 구성됩니다. 쿼리를 생성하는 데 사용됩니다.
FetchableFluentQuery
: FetchableFluentQuery
는 Example
에서 파생된 쿼리를 추가로 사용자 정의할 수 있는 Fetchable API를 제공합니다. Fluent API를 사용하면 쿼리에 대한 순서 예측 및 결과 처리를 지정할 수 있습니다.
QBE는 여러 사용 사례에 매우 적합합니다.
일련의 정적 또는 동적 제약 조건을 사용하여 데이터 store를 쿼리합니다.
기존 쿼리 중단에 대한 걱정 없이 도메인 개체를 자주 리팩터링합니다.
기본 데이터 store API와 독립적으로 작동합니다.
사례별 쿼리에는 다음과 같은 몇 가지 제한 사항도 있습니다.
firstname = ?0 or (fistname =?1 and lastname = ?2)
와 같은 중첩되거나 그룹화된 속성 제약 조건은 지원되지 않습니다.
문자열 일치에 대한 store별 지원. 데이터베이스에 따라 문자열 일치는 문자열에 대한 시작/포함/끝/정규식을 지원할 수 있습니다.
다른 property type과 정확히 일치합니다.
사례별 쿼리를 시작하기 전에 도메인 개체가 있어야 합니다. 시작하려면 다음 예와 같이 repository에 대한 인터페이스를 생성하십시오.
Sample Person object
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
앞의 예에서는 간단한 도메인 개체를 보여줍니다. 이를 사용하여 Example
를 만들 수 있습니다. 기본적으로 null
값이 있는 필드는 무시되고 문자열은 store별 기본값을 사용하여 일치됩니다.
[Note]
Query by Example criteria 에 속성을 포함하는 것은 Null 허용 여부를 기반으로 합니다. 기본 유형(int
,double
, …)을 사용하는 속성은ExampleMatcher
가 속성 경로를 무시하지 않는 한 항상 포함됩니다.
Examples는 of
팩토리 메서드를 사용하거나 ExampleMatcher
를 사용하여 빌드할 수 있습니다. Example
는 변경할 수 없습니다(immutable). 다음 목록은 간단한 예를 보여줍니다.
Example 1. Simple Example
Person person = new Person();
person.setFirstname("Dave");
Example<Person> example = Example.of(person);
(1) 도메인 개체의 새 인스턴스를 만듭니다.
(2) 쿼리할 속성을 설정합니다.
(3) Example를 생성합니다.
리포지토리를 사용하여 예제 쿼리를 실행할 수 있습니다. 이렇게 하려면 저장소 인터페이스가 QueryByExampleExecutor<T>
를 확장하도록 하세요. 다음 목록은 QueryByExampleExecutor
인터페이스에서 발췌한 내용을 보여줍니다.
The QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
Examples는 기본 설정에만 국한되지 않습니다. 다음 예에 표시된 대로 ExampleMatcher
를 사용하여 문자열 일치, null 처리 및 속성별 설정에 대한 고유한 기본값을 지정할 수 있습니다.
Example 2. Example matcher with customized matching
Person person = new Person(); // (1)
person.setFirstname("Dave"); // (2)
ExampleMatcher matcher = ExampleMatcher.matching() // (3)
.withIgnorePaths("lastname") // (4)
.withIncludeNullValues() // (5)
.withStringMatcher(StringMatcher.ENDING); // (6)
Example<Person> example = Example.of(person, matcher); // (7)
(1) 도메인 개체의 새 인스턴스를 만듭니다.
(2) 속성을 설정합니다.
(3) 모든 값이 일치할 것으로 예상하는 ExampleMatcher
를 만듭니다. 추가 구성 없이도 이 단계에서 사용할 수 있습니다.
(4) lastname
속성 경로를 무시하도록 새 ExampleMatcher
를 생성합니다.
(5) lastname
속성 경로를 무시하고 null 값을 포함하도록 새 ExampleMatcher
를 생성합니다.
(6) lastname
속성 경로를 무시하고, null 값을 포함하고, 접미사 문자열 일치를 수행하도록 새 ExampleMatcher
를 생성합니다.
(7) 도메인 객체와 구성된 ExampleMatcher
를 기반으로 새 Example
를 생성합니다.
기본적으로 ExampleMatcher
는 프로브에 설정된 모든 값이 일치할 것으로 예상합니다. 암시적으로 정의된 predicate 중 하나와 일치하는 결과를 얻으려면 ExampleMatcher.matchingAny()
를 사용하세요.
개별 속성(예: "firstname" 및 "lastname" 또는 중첩 속성의 경우 "address.city")에 대한 동작을 지정할 수 있습니다. 다음 예와 같이 일치 옵션과 대소문자 구분을 사용하여 조정할 수 있습니다.
Configuring matcher options
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
matcher 옵션을 구성하는 또 다른 방법은 람다(Java 8에 도입됨)를 사용하는 것입니다. 이 접근 방식은 implementor에게 matcher를 수정하도록 요청하는 콜백을 생성합니다. 구성 옵션이 matcher 인스턴스 내에 보관되므로 matcher를 반환할 필요가 없습니다. 다음 예에서는 람다를 사용하는 매처를 보여줍니다.
Configuring matcher options with lambdas
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
Example
에서 생성된 쿼리는 구성의 merged view를 사용합니다. 기본 matching 설정은 ExampleMatcher
수준에서 설정할 수 있으며, 개별 설정은 특정 속성 경로에 적용될 수 있습니다. ExampleMatcher
에 설정된 설정은 명시적으로 정의되지 않는 한 속성 경로 설정에 의해 상속됩니다. 속성 패치의 설정은 기본 설정보다 우선 순위가 높습니다. 다음 표에서는 다양한 ExampleMatcher
설정의 범위를 설명합니다.
Table 1. Scope of ExampleMatcher settings
설정 | 범위 |
---|---|
Null 처리 |
|
문자열 매칭 |
|
속성 무시 |
속성 경로 |
대소문자 구분 |
|
값 변환 |
속성 경로 |
QueryByExampleExecutor
는 지금까지 언급하지 않은 메서드를 하나 더 제공합니다. <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)
. 다른 방법과 마찬가지로 Example
에서 파생된 쿼리를 실행합니다. 그러나 두 번째 인수를 사용하면 동적으로 제어할 수 없는 실행 측면을 제어할 수 있습니다. 두 번째 인수에서 FetchableFluentQuery
의 다양한 메서드를 호출하면 됩니다. sortBy
를 사용하면 결과의 순서를 지정할 수 있습니다. as
를 사용하면 결과를 변환하려는 유형을 지정할 수 있습니다. project
는 쿼리된 속성을 제한합니다. first
, firstValue
, one
, oneValue
, all
, page
, stream
, count
및 presents
는 얻을 수 있는 결과의 종류와 예상보다 많은 결과를 사용할 수 있을 때 쿼리가 작동하는 방식을 정의합니다.
fluent API를 사용하여 lastname순으로 정렬된 잠재적으로 많은 결과 중 마지막 결과를 얻으세요.
Optional<Person> match = repository.findBy(example,
q -> q
.sortBy(Sort.by("lastname").descending())
.first()
);
Spring Data JPA에서는 다음 예제와 같이 리포지토리와 함께 QBE를 사용할 수 있습니다.
Example 3. Query by Example using a Repository
public interface PersonRepository extends JpaRepository<Person, String> { … }
public class PersonService {
@Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
[Note]
현재SingularAttribute
속성만 속성 일치에 사용할 수 있습니다.
속성 지정자는 속성 이름(예: firstname
, lastname
)을 허용합니다. 속성을 점(address.city
)으로 연결하여 탐색할 수 있습니다. 일치하는 옵션과 대소문자 구분을 사용하여 조정할 수도 있습니다.
다음 표에서는 사용할 수 있는 다양한 StringMatcher
옵션과 이를 firstname
이라는 필드에 사용한 결과를 보여줍니다.
Table 2. StringMatcher options
매칭 | 논리적 결과 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Note]
정규식 matching은 JPA에서 지원되지 않습니다.