案例概述在本教程中,我们将研究使用Spring Data JPA和Querydsl为REST API构建查询语言。在本系列的前两篇文章中,我们使用JPA Criteria和Spring Data JPA规范构建了相同的搜索/过滤功能。那么 - 为什么要使用查询语言?因为 - 对于任何复杂的API来说 - 通过非常简单的字段搜索/过滤资源是不够的。查询语言更灵活,允许您精确过滤所需的资源。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 - Annotation处理工具 - 插件如下:<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>MyUser Entity接下来 - 让我们看一下我们将在Search API中使用的“MyUser”实体:@Entitypublic class MyUser {    @Id    @GeneratedValue(strategy = GenerationType.AUTO)    private Long id;     private String firstName;    private String lastName;    private String email;     private int age;}使用PathBuilder自定义Predicate现在 - 让我们根据一些任意约束创建一个自定义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;    }}请注意Predicate的实现是通常如何处理多种类型的操作。这是因为查询语言根据定义是一种开放式语言,您可以使用任何支持的操作对任何字段进行过滤。为了表示这种开放式过滤标准,我们使用了一个简单但非常灵活的实现 - SearchCriteria:public class SearchCriteria {    private String key;    private String operation;    private Object value;}key:用于保存字段名称 - 例如:firstName,age,…等。operation:用于保持操作 - 例如:Equality,less,…等。value:用于保存字段值 - 例如:john,25,…等。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);      }}结合Predicates接下来让我们看看组合Predicates在结果过滤中使用多个约束。在以下示例中 - 我们使用构建器 - MyUserPredicatesBuilder - 来组合Predicates: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<BooleanExpression> predicates = new ArrayList<>();        MyUserPredicate predicate;        for (SearchCriteria param : params) {            predicate = new MyUserPredicate(param);            BooleanExpression exp = predicate.getPredicate();            if (exp != null) {                predicates.add(exp);            }        }         BooleanExpression result = predicates.get(0);        for (int i = 1; i < predicates.size(); i++) {            result = result.and(predicates.get(i));        }        return result;    }}测试搜索查询接下来 - 让我们测试一下我们的Search API。我们将首先使用少数用户初始化数据库 - 准备好这些数据并进行测试:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { PersistenceConfig.class })@Transactional@Rollbackpublic 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(“john@doe.com”);        userJohn.setAge(22);        repo.save(userJohn);         userTom = new MyUser();        userTom.setFirstName(“Tom”);        userTom.setLastName(“Doe”);        userTom.setEmail(“tom@doe.com”);        userTom.setAge(26);        repo.save(userTom);    }}接下来,让我们看看如何查找具有给定姓氏的用户:@Testpublic void givenLast_whenGettingListOfUsers_thenCorrect() {    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with(“lastName”, “:”, “Doe”);     Iterable<MyUser> results = repo.findAll(builder.build());    assertThat(results, containsInAnyOrder(userJohn, userTom));}现在,让我们看看如何找到具有名字和姓氏的用户:@Testpublic 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)));}接下来,让我们看看如何找到具有姓氏和最小年龄的用户@Testpublic 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)));}接下来,让我们搜索实际不存在的用户:@Testpublic 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 - 如下例所示:@Testpublic 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)));}UserController最后,让我们将所有内容放在一起并构建REST API。我们定义了一个UserController,它定义了一个带有“search”参数的简单方法findAll()来传递查询字符串:@Controllerpublic 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”:“tom@doe.com”,    “age”:26}]案例结论第三篇文章介绍了为REST API构建查询语言的第一步,充分利用了Querydsl库。