Spring Data JPA REST Query QueryDSL

案例概述java

在本教程中,咱们将研究使用Spring Data JPA和Querydsl为REST API构建查询语言。spring

本系列的前两篇文章中,咱们使用JPA Criteria和Spring Data JPA规范构建了相同的搜索/过滤功能。数据库

那么 - 为何要使用查询语言?由于 - 对于任何复杂的API来讲 - 经过很是简单的字段搜索/过滤资源是不够的。查询语言更灵活,容许您精确过滤所需的资源。json

Querydsl配置bash

首先 - 让咱们看看如何配置咱们的项目以使用Querydsl。app

咱们须要将如下依赖项添加到pom.xml:less

<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处理工具 - 插件以下:maven

<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 Entityide

接下来 - 让咱们看一下咱们将在Search API中使用的“MyUser”实体:工具

@Entity
public 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
@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("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);
    }
}
复制代码

接下来,让咱们看看如何查找具备给定姓氏的用户

@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)));
}
复制代码

接下来,让咱们搜索实际不存在的用户:

@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)));
}
复制代码

UserController

最后,让咱们将全部内容放在一块儿并构建REST API。

咱们定义了一个UserController,它定义了一个带有“search”参数的简单方法findAll()来传递查询字符串:

@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":"tom@doe.com",
    "age":26
}]
复制代码

案例结论

第三篇文章介绍了为REST API构建查询语言的第一步,充分利用了Querydsl库。

相关文章
相关标签/搜索