RESTクエリ言語-OR操作の実装
1. 概要
この簡単な記事では、previous articleに実装した高度な検索操作を拡張し、OR-based search criteria into our REST API Query Languageを含めます。
2. 実装アプローチ
以前は、searchクエリパラメータのすべての基準は、AND演算子によってのみグループ化された述語を形成していました。 それを変えましょう。
この機能は、既存のアプローチへの簡単で迅速な変更として、またはゼロから新しいアプローチとして実装できる必要があります。
簡単なアプローチでは、OR演算子を使用して組み合わせる必要があることを示すために、基準にフラグを立てます。
たとえば、APIで「firstName OR lastName”:」をテストするためのURLは次のとおりです。
http://localhost:8080/users?search=firstName:john,'lastName:doe
基準lastNameに、区別するために一重引用符でフラグを付けていることに注意してください。 OR演算子のこの述語を基準値オブジェクト–SpecSearchCriteria:にキャプチャします
public SpecSearchCriteria(
String orPredicate, String key, SearchOperation operation, Object value) {
super();
this.orPredicate
= orPredicate != null
&& orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG);
this.key = key;
this.operation = operation;
this.value = value;
}
3. UserSpecificationBuilderの改善
それでは、仕様ビルダーUserSpecificationBuilder,を変更して、Specification<User>を作成するときにOR修飾基準を考慮してみましょう。
public Specification build() {
if (params.size() == 0) {
return null;
}
Specification result = new UserSpecification(params.get(0));
for (int i = 1; i < params.size(); i++) {
result = params.get(i).isOrPredicate()
? Specification.where(result).or(new UserSpecification(params.get(i)))
: Specification.where(result).and(new UserSpecification(params.get(i)));
}
return result;
}
4. UserControllerの改善
最後に、この検索機能をOR演算子で使用するために、コントローラーに新しいRESTエンドポイントを設定しましょう。 改良された解析ロジックは、OR演算子で基準を識別するのに役立つ特別なフラグを抽出します。
@GetMapping("/users/espec")
@ResponseBody
public List findAllByOrPredicate(@RequestParam String search) {
Specification spec = resolveSpecification(search);
return dao.findAll(spec);
}
protected Specification resolveSpecification(String searchParameters) {
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
String operationSetExper = Joiner.on("|")
.join(SearchOperation.SIMPLE_OPERATION_SET);
Pattern pattern = Pattern.compile(
"(\\p{Punct}?)(\\w+?)("
+ operationSetExper
+ ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
Matcher matcher = pattern.matcher(searchParameters + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3),
matcher.group(5), matcher.group(4), matcher.group(6));
}
return builder.build();
}
5. OR条件でのライブテスト
このライブテストの例では、新しいAPIエンドポイントを使用して、名「john」または姓「doe」でユーザーを検索します。 パラメータlastNameには一重引用符があり、「OR述語」として修飾されていることに注意してください。
private String EURL_PREFIX
= "http://localhost:8082/spring-rest-full/auth/users/espec?search=";
@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
Response response = givenAuth().get(EURL_PREFIX + "firstName:john,'lastName:doe");
String result = response.body().asString();
assertTrue(result.contains(userJohn.getEmail()));
assertTrue(result.contains(userTom.getEmail()));
}
6. OR条件での永続性テスト
ここで、ユーザーwith first name “john” OR last name “doe”の永続性レベルで、上記と同じテストを実行してみましょう。
@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
SpecSearchCriteria spec
= new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john");
SpecSearchCriteria spec1
= new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe");
List results = repository
.findAll(builder.with(spec).with(spec1).build());
assertThat(results, hasSize(2));
assertThat(userJohn, isIn(results));
assertThat(userTom, isIn(results));
}
7. 代替アプローチ
別のアプローチでは、SQLクエリの完全なWHERE句のように検索クエリを提供できます。
たとえば、firstNameとage:によるより複雑な検索のURLは次のとおりです。
http://localhost:8080/users?search=( firstName:john OR firstName:tom ) AND age>22
個々の基準、演算子を分離していることに注意してください
CriteriaParserを使用して中置式を解析してみましょう。 CriteriaParserは、指定された中置式をトークン(基準、括弧、AND&OR演算子)に分割し、同じ後置式を作成します。
public Deque> parse(String searchParam) {
Deque
仕様ビルダーに新しいメソッドGenericSpecificationBuilder,を追加して、接尾辞式から検索Specificationを作成しましょう。
public Specification build(Deque> postFixedExprStack,
Function> converter) {
Deque> specStack = new LinkedList<>();
while (!postFixedExprStack.isEmpty()) {
Object mayBeOperand = postFixedExprStack.pollLast();
if (!(mayBeOperand instanceof String)) {
specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand));
} else {
Specification operand1 = specStack.pop();
Specification operand2 = specStack.pop();
if (mayBeOperand.equals(SearchOperation.AND_OPERATOR)) {
specStack.push(Specification.where(operand1)
.and(operand2));
}
else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR)) {
specStack.push(Specification.where(operand1)
.or(operand2));
}
}
}
return specStack.pop();
最後に、UserControllerに別のRESTエンドポイントを追加して、新しいCriteriaParserで複雑な式を解析します。
@GetMapping("/users/spec/adv")
@ResponseBody
public List findAllByAdvPredicate(@RequestParam String search) {
Specification spec = resolveSpecificationFromInfixExpr(search);
return dao.findAll(spec);
}
protected Specification resolveSpecificationFromInfixExpr(String searchParameters) {
CriteriaParser parser = new CriteriaParser();
GenericSpecificationsBuilder specBuilder = new GenericSpecificationsBuilder<>();
return specBuilder.build(parser.parse(searchParameters), UserSpecification::new);
}
8. 結論
このチュートリアルでは、OR演算子を使用して検索する機能を備えたRESTクエリ言語を改善しました。
この記事の完全な実装はthe GitHub projectにあります。 これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。