知識庫 / Spring / Spring Data RSS 訂閱

Spring Data JPA 和 Querydsl 中文查詢語言

REST,Spring Data
HongKong
9
04:11 AM · Dec 06 ,2025
<div>
 <a class="article-series-header" href="javascript:void(0);">該文章是系列的一部分</a>
 <div>
  <div>
   • 使用 Spring 和 JPA Criteria 構建 REST 查詢語言
   <br>
   • 使用 Spring Data JPA Specifications 構建 REST 查詢語言
   <br>
   <div>
    • 使用 Spring Data JPA 和 Querydsl 構建 REST 查詢語言(當前文章)
   </div>
   • 高級搜索操作 – REST 查詢語言
   <br>
   • REST 查詢語言 – 實現 OR 運算
   <br>
   • 使用 RSQL 構建 REST 查詢語言
   <br>
   • 使用 Querydsl Web 支持構建 REST 查詢語言
   <br>
  </div>
  <!-- end of article series inner -->
 </div>
 <!-- .article-series-links -->
</div>
<!-- end of article series section -->

1. 概述

在本教程中,我們將探討如何使用 Spring Data JPA 和 Querydsl 構建一個查詢語言 用於 REST API

在本次系列的前兩篇文章中,我們已經使用 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 Entity

接下來,讓我們看一下“”實體,我們將使用它在我們的搜索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. 自定義PredicatePathBuilder

現在,讓我們創建一個基於任意約束條件的自定義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:字段名稱 – 例如:firstNameage
  • 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);
      }
}

請注意,我們這裏使用的是為 MyUser 實體生成的 Q 類型的格式,它將被命名為 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 庫。

實現目前仍處於早期階段,但可以輕鬆地擴展以支持更多操作。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.