データ]
-
リンク:/tag/jpa/[JPA]
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プロジェクト]にあります。インポートしてそのまま実行するのは簡単なはずです。
次 "
-
«** 前へ