Spring Data JPA仕様のRESTクエリ言語

データ]

1概要

このチュートリアルでは - Spring Data JPAとSpecificationsを使って Search/Filter REST API を構築します。

私達はリンクの問い合わせ言語を見始めました: JPA基準ベースのソリューション。

だから - なぜクエリー言語なのか 複雑で十分なAPIの場合、非常に単純なフィールドでリソースを検索/フィルタリングするだけでは不十分です。 問い合わせ言語はより柔軟です** そしてあなたが正確にあなたが必要とするリソースに絞り込むことを可能にします。

2 ユーザー エンティティ

まず、検索API用の単純な User エンティティから始めましょう。

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

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

    private int age;

   //standard getters and setters
}

3 Specification を使ったフィルタ

それでは、問題の最も興味深い部分、つまりカスタムのSpring Data JPA Specifications を使用して照会してみましょう。

Specification インターフェースを実装する UserSpecification を作成し、実際のクエリを構築するために 独自の制約を渡します :

public class UserSpecification implements Specification<User> {

    private SearchCriteria criteria;

    @Override
    public Predicate toPredicate
      (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {

        if (criteria.getOperation().equalsIgnoreCase(">")) {
            return builder.greaterThanOrEqualTo(
              root.<String> get(criteria.getKey()), criteria.getValue().toString());
        }
        else if (criteria.getOperation().equalsIgnoreCase("<")) {
            return builder.lessThanOrEqualTo(
              root.<String> get(criteria.getKey()), criteria.getValue().toString());
        }
        else if (criteria.getOperation().equalsIgnoreCase(":")) {
            if (root.get(criteria.getKey()).getJavaType() == String.class) {
                return builder.like(
                  root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
            } else {
                return builder.equal(root.get(criteria.getKey()), criteria.getValue());
            }
        }
        return null;
    }
}

ご覧のとおり - いくつかの単純な制約に基づいて Specification を作成します これを次の“ SearchCriteria ”クラスで表します。

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

SearchCriteria 実装は制約の基本的な表現を保持しています - そしてそれはこの制約に基づいています。

  • key :フィールド名 - たとえば、 firstName age など

  • 操作 :操作 - 例えば、等価、小なり、…​など

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

もちろん、実装は単純化されており改善することができます。しかしながら、それは我々が必要とする強力で柔軟なオペレーションのための確かな基盤です。

4 UserRepository

次に、 UserRepository を見てみましょう。新しい仕様APIを取得するには、単に JpaSpecificationExecutor を拡張します。

public interface UserRepository
  extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}

5検索クエリをテストする

それでは、新しい検索APIを試してみましょう。

まず、テストの実行時に準備を整えるために、少数のユーザーを作成しましょう。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceJPAConfig.class })
@Transactional
@TransactionConfiguration
public class JPASpecificationsTest {

    @Autowired
    private UserRepository repository;

    private User userJohn;
    private User userTom;

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

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

次に、 姓が のユーザーを検索する方法を見てみましょう。

@Test
public void givenLast__whenGettingListOfUsers__thenCorrect() {
    UserSpecification spec =
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));

    List<User> results = repository.findAll(spec);

    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));
}

それでは、名 と姓 の両方を持つユーザーを見つける方法を見てみましょう。

@Test
public void givenFirstAndLastName__whenGettingListOfUsers__thenCorrect() {
    UserSpecification spec1 =
      new UserSpecification(new SearchCriteria("firstName", ":", "john"));
    UserSpecification spec2 =
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));

    List<User> results = repository.findAll(Specification.where(spec1).and(spec2));

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

注:仕様を組み合わせるために、 " where "と " and "を使用しました。

次に、苗字と最低年齢** の両方を持つユーザーを見つける方法を見てみましょう。

@Test
public void givenLastAndAge__whenGettingListOfUsers__thenCorrect() {
    UserSpecification spec1 =
      new UserSpecification(new SearchCriteria("age", ">", "25"));
    UserSpecification spec2 =
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));

    List<User> results =
      repository.findAll(Specification.where(spec1).and(spec2));

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

では、 実際には存在しない ユーザー__を検索する方法を見てみましょう。

@Test
public void givenWrongFirstAndLast__whenGettingListOfUsers__thenCorrect() {
    UserSpecification spec1 =
      new UserSpecification(new SearchCriteria("firstName", ":", "Adam"));
    UserSpecification spec2 =
      new UserSpecification(new SearchCriteria("lastName", ":", "Fox"));

    List<User> results =
      repository.findAll(Specification.where(spec1).and(spec2));

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

最後に - 名 の一部にのみ指定された User を見つける方法を見てみましょう。

@Test
public void givenPartialFirst__whenGettingListOfUsers__thenCorrect() {
    UserSpecification spec =
      new UserSpecification(new SearchCriteria("firstName", ":", "jo"));

    List<User> results = repository.findAll(spec);

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

6. 組み合わせ 仕様

次に、カスタムの「仕様」を組み合わせて複数の制約を使用し、複数の基準に従ってフィルタ処理する方法を見てみましょう。

Specifications を簡単かつ流暢に組み合わせるために、 UserSpecificationsBuilder というビルダーを実装します。

public class UserSpecificationsBuilder {

    private final List<SearchCriteria> params;

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

    public UserSpecificationsBuilder with(String key, String operation, Object value) {
        params.add(new SearchCriteria(key, operation, value));
        return this;
    }

    public Specification<User> build() {
        if (params.size() == 0) {
            return null;
        }

        List<Specification> specs = params.stream()
          .map(UserSpecification::new)
          .collect(Collectors.toList());

        Specification result = specs.get(0);

        for (int i = 1; i < params.size(); i++) {
            result = params.get(i)
              .isOrPredicate()
                ? Specification.where(result)
                  .or(specs.get(i))
                : Specification.where(result)
                  .and(specs.get(i));
        }
        return result;
    }
}

7. UserController

最後に、単純な search 操作で UserController を作成して、この新しい持続性検索/フィルタ機能を使用し、 REST APIを設定しましょう

@Controller
public class UserController {

    @Autowired
    private UserRepository repo;

    @RequestMapping(method = RequestMethod.GET, value = "/users")
    @ResponseBody
    public List<User> search(@RequestParam(value = "search") String search) {
        UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
        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));
        }

        Specification<User> spec = builder.build();
        return repo.findAll(spec);
    }
}

他の英語以外のシステムをサポートするために、 Pattern オブジェクトを次のように変更することができます。

Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),", Pattern.UNICODE__CHARACTER__CLASS);

これは、APIをテストするためのテストURLの例です。

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

そして応答:

----[{
    "id":2,
    "firstName":"tom",
    "lastName":"doe",
    "email":"[email protected]",
    "age":26
}]----
  • Pattern の例では、検索が「、」で区切られているため、検索語にこの文字を含めることはできません。** パターンも空白と一致しません。

コンマを含む値を検索したい場合は、「;」などの別の区切り文字を使用することを検討できます。

もう1つの選択肢は、引用符の間の値を検索するようにパターンを変更してから、検索語からこれらを削除することです。

Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\"([^\"]+)\")");

8結論

このチュートリアルでは、強力なRESTクエリ言語の基礎となる簡単な実装について説明しました。 Spring Data Specificationを利用して、APIをドメインから遠ざけ、他の多くの種類の操作を処理できるようにしています。

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

"

  • «** 前へ