Spring Data JPA 和 Querydsl 中文查詢語言
這篇文章是系列的一部分:
• 使用 Spring 和 JPA Criteria 的 REST 查詢語言
• 使用 Spring Data JPA Specifications 的 REST 查詢語言
• 使用 Spring Data JPA 和 Querydsl 的 REST 查詢語言(當前文章)
• REST 查詢語言 – 高級搜索操作
• REST 查詢語言 – 實現 OR 操作
• REST 查詢語言與 RSQL
• REST 查詢語言與 Querydsl Web 支持
1. 概述
在本教程中,我們將構建一個用於 REST API 的查詢語言,使用 Spring Data JPA 和 Querydsl
在本文檔系列的前兩篇中,我們使用 JPA Criteria 和 Spring Data JPA Specifications 構建了相同的搜索/過濾功能。
因此——為什麼需要一個查詢語言?
因為——對於足夠複雜且任何 API 來説,僅通過簡單的字段搜索/過濾資源已經不夠。查詢語言更靈活,並允許您過濾到您需要的資源。
2. Querydsl 配置
首先,讓我們看看如何配置項目以使用 Querydsl。
我們需要將以下依賴項添加到 pom.xml
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>4.2.2</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>
這將為我們的實體生成 Q-類型。
3. The MyUser Entity
接下來,讓我們來查看“MyUser”實體,我們將使用它在我們的搜索API中:
@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. 自定義 Predicate W 使用 PathBuilder
現在,讓我們創建一個基於某些任意約束的自定義 Predicate。
我們這裏使用 PathBuilder 而不是自動生成的 Q-類型,因為我們需要為更抽象的使用動態創建路徑:
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 等等
- operation:操作——例如:相等、小於等
- value:字段值——例如:john、25 等等
5. MyUserRepository
現在,讓我們來查看我們的 MyUserRepository。
我們需要我們的 MyUserRepository 擴展 QuerydslPredicateExecutor,以便我們稍後可以使用 Predicates 來過濾搜索結果:
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);
}
}
請注意,我們在這裏使用了生成的 Q-類型用於 MyUser 實體,它將被命名為 QMyUser。
6. 組合 謂詞
接下來,讓我們看看如何組合謂詞,以在結果過濾中使用多個約束。
在以下示例中——我們使用構建器——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。
我們定義了一個 UserController,它定義了一個簡單的 findAll() 方法,帶有“search” 參數用於傳遞查詢字符串:
@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. 結論
本文介紹了構建 REST API 查詢語言的初步步驟,充分利用了 Querydsl 庫。
目前實現仍處於早期階段,但可以很容易地擴展以支持更多操作。
« 之前的
REST 查詢語言與 Spring Data JPA Specifications
0 位用戶收藏了這個故事!