データ]


1概要

このチュートリアルでは、Spring Data JPAとQuerydsl

を使用して

REST API用のクエリ言語を構築することを検討しています。

リンクの最初の2つの記事:/spring-rest-api-query-search-language-tutorial[このシリーズ]では、JPA CriteriaとSpring Data JPA Specificationsを使用して同じ検索/フィルタリング機能を構築しました。

それで –

なぜクエリ言語なのか?

– 複雑なAPIに対しては、非常に単純なフィールドでリソースを検索/フィルタリングするだけでは不十分です。

クエリ言語はより柔軟です

、そしてあなたが正確に必要なリソースに絞り込むことができます。


2 Querydslの設定

まず、Querydslを使用するようにプロジェクトを構成する方法を見てみましょう。


pom.xml

に次の依存関係を追加する必要があります。

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.1.4</version>
    </dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.1.4</version>
</dependency>

APT – 注釈処理ツール – プラグインを次のように設定する必要もあります。

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>


3

MyUser

エンティティ

次に、検索APIで使用する予定の「

MyUser

」エンティティを見てみましょう。

@Entity
public class MyUser {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;

    private int age;
}


4

PathBuilder


を使用したカスタム

Predicate

それでは、いくつかの任意の制約に基づいてカスタム

Predicate

を作成しましょう。

自動生成されたQ-typeの代わりにここでは

PathBuilder

を使用しています。より抽象的な使用法のために動的にパスを作成する必要があるからです。

public class MyUserPredicate {

    private SearchCriteria criteria;

    public BooleanExpression getPredicate() {
        PathBuilder<MyUser> entityPath = new PathBuilder<>(MyUser.class, "user");

        if (isNumeric(criteria.getValue().toString())) {
            NumberPath<Integer> path = entityPath.getNumber(criteria.getKey(), Integer.class);
            int value = Integer.parseInt(criteria.getValue().toString());
            switch (criteria.getOperation()) {
                case ":":
                    return path.eq(value);
                case ">":
                    return path.goe(value);
                case "<":
                    return path.loe(value);
            }
        }
        else {
            StringPath path = entityPath.getString(criteria.getKey());
            if (criteria.getOperation().equalsIgnoreCase(":")) {
                return path.containsIgnoreCase(criteria.getValue().toString());
            }
        }
        return null;
    }
}

述語の実装がどのように

複数の種類の操作を

一般的に扱うかに注意してください。これは、クエリ言語が定義上、サポートされている操作を使用して、任意のフィールドでフィルタをかけることができるオープン言語であるためです。

そのようなオープンなフィルター基準を表すために、私たちは単純だが非常に柔軟な実装を使用しています –

SearchCriteria

:

public class SearchCriteria {
    private String key;
    private String operation;
    private Object value;
}


SearchCriteria

は、制約を表すために必要な詳細を保持しています。


  • key

    :フィールド名 – 例:

    firstName



    age

    、…​など


  • 操作

    :操作 – 例:等価、小なり、…​など


  • value

    :フィールド値 – 例えば、john、25、…​など


5

MyUserRepository


それでは、

MyUserRepository

を見てみましょう。

後で

Predicates

を使用して検索結果をフィルタ処理できるように、

QuerydslPredicateExecutor

を拡張するには、

MyUserRepository

が必要です。

public interface MyUserRepository extends JpaRepository<MyUser, Long>,
  QuerydslPredicateExecutor<MyUser>, QuerydslBinderCustomizer<QMyUser> {
    @Override
    default public void customize(
      QuerydslBindings bindings, QMyUser root) {
        bindings.bind(String.class)
          .first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
        bindings.excluding(root.email);
      }
}


6.

予測


を組み合わせる

次に、述語を組み合わせて結果フィルタリングで複数の制約を使用する方法を見てみましょう。

次の例では、

Predicates

を組み合わせるためにビルダー

MyUserPredicatesBuilder

を使用します。

public class MyUserPredicatesBuilder {
    private List<SearchCriteria> params;

    public MyUserPredicatesBuilder() {
        params = new ArrayList<>();
    }

    public MyUserPredicatesBuilder with(
      String key, String operation, Object value) {

        params.add(new SearchCriteria(key, operation, value));
        return this;
    }

    public BooleanExpression build() {
        if (params.size() == 0) {
            return null;
        }

        List predicates = params.stream().map(param -> {
            MyUserPredicate predicate = new MyUserPredicate(param);
            return predicate.getPredicate();
        }).filter(Objects::nonNull).collect(Collectors.toList());

        BooleanExpression result = Expressions.asBoolean(true).isTrue();
        for (BooleanExpression predicate : predicates) {
            result = result.and(predicate);
        }
        return result;
    }
}


7. 検索クエリをテストする

次に、検索APIをテストしましょう。

私たちは、少数のユーザーでデータベースを初期化することから始めます – これらをテストの準備ができて利用できるようにするために:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceConfig.class })
@Transactional
@Rollback
public class JPAQuerydslIntegrationTest {

    @Autowired
    private MyUserRepository repo;

    private MyUser userJohn;
    private MyUser userTom;

    @Before
    public void init() {
        userJohn = new MyUser();
        userJohn.setFirstName("John");
        userJohn.setLastName("Doe");
        userJohn.setEmail("[email protected]");
        userJohn.setAge(22);
        repo.save(userJohn);

        userTom = new MyUser();
        userTom.setFirstName("Tom");
        userTom.setLastName("Doe");
        userTom.setEmail("[email protected]");
        userTom.setAge(26);
        repo.save(userTom);
    }
}

次に、

姓が

のユーザーを検索する方法を見てみましょう。

@Test
public void givenLast__whenGettingListOfUsers__thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe");

    Iterable<MyUser> results = repo.findAll(builder.build());
    assertThat(results, containsInAnyOrder(userJohn, userTom));
}

それでは、名

と姓

の両方を持つユーザーを見つける方法を見てみましょう。

@Test
public void givenFirstAndLastName__whenGettingListOfUsers__thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("firstName", ":", "John").with("lastName", ":", "Doe");

    Iterable<MyUser> results = repo.findAll(builder.build());

    assertThat(results, contains(userJohn));
    assertThat(results, not(contains(userTom)));
}

次に、

名と

年齢の両方を指定して** ユーザーを見つける方法を見てみましょう。

@Test
public void givenLastAndAge__whenGettingListOfUsers__thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("lastName", ":", "Doe").with("age", ">", "25");

    Iterable<MyUser> results = repo.findAll(builder.build());

    assertThat(results, contains(userTom));
    assertThat(results, not(contains(userJohn)));
}

それでは、実際には存在しない

MyUser

を検索する方法を見てみましょう。

@Test
public void givenWrongFirstAndLast__whenGettingListOfUsers__thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("firstName", ":", "Adam").with("lastName", ":", "Fox");

    Iterable<MyUser> results = repo.findAll(builder.build());
    assertThat(results, emptyIterable());
}

最後の例 – 次の例のように、姓

の一部にのみ指定された

MyUser


を見つける方法を見てみましょう。

@Test
public void givenPartialFirst__whenGettingListOfUsers__thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo");

    Iterable<MyUser> results = repo.findAll(builder.build());

    assertThat(results, contains(userJohn));
    assertThat(results, not(contains(userTom)));
}


8

UserController


最後に、すべてをまとめてREST APIを構築しましょう。

クエリ文字列を渡すための“

search

”パラメータを持つ単純なメソッド

findAll()

を定義する

UserController

を定義します。

@Controller
public class UserController {

    @Autowired
    private MyUserRepository myUserRepository;

    @RequestMapping(method = RequestMethod.GET, value = "/myusers")
    @ResponseBody
    public Iterable<MyUser> search(@RequestParam(value = "search") String search) {
        MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();

        if (search != null) {
            Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),");
            Matcher matcher = pattern.matcher(search + ",");
            while (matcher.find()) {
                builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
            }
        }
        BooleanExpression exp = builder.build();
        return myUserRepository.findAll(exp);
    }
}

これは簡単なテストURLの例です。

http://localhost:8080/myusers?search=lastName:doe,age>25

そして応答:

----[{
    "id":2,
    "firstName":"tom",
    "lastName":"doe",
    "email":"[email protected]",
    "age":26
}]----


9結論

この3番目の記事では、

Querydslライブラリーをうまく利用して

REST API用の照会言語を作成する** 最初のステップについて説明しました。

実装はもちろん早い段階ですが、追加操作をサポートするために容易に進化させることができます。

この記事の

完全な実装

はhttps://github.com/eugenp/tutorials/tree/master/spring-rest-query-language[GitHubプロジェクト]にあります。インポートしてそのまま実行するのは簡単なはずです。




  • «** 前へ