REST Query Language with Spring and JPA Criteria – 使用Spring和JPA标准的REST查询语言

最后修改: 2015年 1月 27日


1. Overview


In this first article of this new series, we’ll explore a simple query language for a REST API. We’ll make good use of Spring for the REST API and JPA 2 Criteria for the persistence aspects.

这个新系列的第一篇文章中,我们将探讨REST API的简单查询语言。我们将在REST API方面充分利用Spring,在持久化方面利用JPA 2 Criteria。

Why a query language? Because – for any complex enough API – searching/filtering your resources by very simple fields is simply not enough. A query language is more flexible, and allows you to filter down to exactly the resources you need.


2. User Entity


First – let’s put forward the simple entity that we’re going to use for our filter/search API – a basic User:

首先,让我们提出一个简单的实体,我们将用于我们的过滤器/搜索API – 一个基本的User

public class User {
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;
    private String lastName;
    private String email;

    private int age;

3. Filter Using CriteriaBuilder


Now – let’s get into the meat of the problem – the query in the persistence layer.


Building a query abstraction is a matter of balance. We need a good amount of flexibility on the one hand, and we need to keep complexity manageable on the other. High level, the functionality is simple – you pass in some constraints and you get back some results.


Let’s see how that works:


public class UserDAO implements IUserDAO {

    private EntityManager entityManager;

    public List<User> searchUser(List<SearchCriteria> params) {
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root r = query.from(User.class);

        Predicate predicate = builder.conjunction();

        UserSearchQueryCriteriaConsumer searchConsumer = 
          new UserSearchQueryCriteriaConsumer(predicate, builder, r);;
        predicate = searchConsumer.getPredicate();

        List<User> result = entityManager.createQuery(query).getResultList();
        return result;

    public void save(User entity) {

Let’s have a look at the UserSearchQueryCriteriaConsumer class:


public class UserSearchQueryCriteriaConsumer implements Consumer<SearchCriteria>{

    private Predicate predicate;
    private CriteriaBuilder builder;
    private Root r;

    public void accept(SearchCriteria param) {
        if (param.getOperation().equalsIgnoreCase(">")) {
            predicate = builder.and(predicate, builder
              .greaterThanOrEqualTo(r.get(param.getKey()), param.getValue().toString()));
        } else if (param.getOperation().equalsIgnoreCase("<")) {
            predicate = builder.and(predicate, builder.lessThanOrEqualTo(
              r.get(param.getKey()), param.getValue().toString()));
        } else if (param.getOperation().equalsIgnoreCase(":")) {
            if (r.get(param.getKey()).getJavaType() == String.class) {
                predicate = builder.and(predicate,
                  r.get(param.getKey()), "%" + param.getValue() + "%"));
            } else {
                predicate = builder.and(predicate, builder.equal(
                  r.get(param.getKey()), param.getValue()));

    // standard constructor, getter, setter

As you can see, the searchUser API takes a list of very simple constraints, composes a query based on these constraints, does the search and returns the results.

正如你所看到的,searchUser API需要一个非常简单的约束条件列表,根据这些约束条件组成一个查询,进行搜索并返回结果。

The constraint class is quite simple as well:


public class SearchCriteria {
    private String key;
    private String operation;
    private Object value;

The SearchCriteria implementation holds our Query parameters:


  • key: used to hold field name – for example: firstName, age, … etc.
  • operation: used to hold the operation – for example: Equality, less than, … etc.
  • value: used to hold the field value – for example: john, 25, … etc.

4. Test the Search Queries


Now – let’s test our search mechanism to make sure it holds water.


First – let’s initialize our database for testing by adding two users – as in the following example:


@ContextConfiguration(classes = { PersistenceConfig.class })
public class JPACriteriaQueryTest {

    private IUserDAO userApi;

    private User userJohn;

    private User userTom;

    public void init() {
        userJohn = new User();

        userTom = new User();

Now, let’s get a User with specific firstName and lastName – as in the following example:


public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
    List<SearchCriteria> params = new ArrayList<SearchCriteria>();
    params.add(new SearchCriteria("firstName", ":", "John"));
    params.add(new SearchCriteria("lastName", ":", "Doe"));

    List<User> results = userApi.searchUser(params);

    assertThat(userJohn, isIn(results));
    assertThat(userTom, not(isIn(results)));

Next, let’s get a List of User with the same lastName:


public void givenLast_whenGettingListOfUsers_thenCorrect() {
    List<SearchCriteria> params = new ArrayList<SearchCriteria>();
    params.add(new SearchCriteria("lastName", ":", "Doe"));

    List<User> results = userApi.searchUser(params);
    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));

Next, let’s get users with age greater than or equal 25:


public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
    List<SearchCriteria> params = new ArrayList<SearchCriteria>();
    params.add(new SearchCriteria("lastName", ":", "Doe"));
    params.add(new SearchCriteria("age", ">", "25"));

    List<User> results = userApi.searchUser(params);

    assertThat(userTom, isIn(results));
    assertThat(userJohn, not(isIn(results)));

Next, let’s search for users that don’t actually exist:


public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
    List<SearchCriteria> params = new ArrayList<SearchCriteria>();
    params.add(new SearchCriteria("firstName", ":", "Adam"));
    params.add(new SearchCriteria("lastName", ":", "Fox"));

    List<User> results = userApi.searchUser(params);
    assertThat(userJohn, not(isIn(results)));
    assertThat(userTom, not(isIn(results)));

Finally, let’s search for users given only partial firstName:


public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
    List<SearchCriteria> params = new ArrayList<SearchCriteria>();
    params.add(new SearchCriteria("firstName", ":", "jo"));

    List<User> results = userApi.searchUser(params);

    assertThat(userJohn, isIn(results));
    assertThat(userTom, not(isIn(results)));

6. The UserController


Finally, let’s now wire in the persistence support for this flexible search to our REST API.

最后,让我们现在把这个灵活的搜索的持久性支持连接到我们的REST API。

We’re going to be setting up a simple UserController – with a findAll() using the “search” to pass in the entire search/filter expression:

我们将设置一个简单的UserController – 带有findAll() 使用”search“来传入整个搜索/过滤表达式

public class UserController {

    private IUserDao api;

    @RequestMapping(method = RequestMethod.GET, value = "/users")
    public List<User> findAll(@RequestParam(value = "search", required = false) String search) {
        List<SearchCriteria> params = new ArrayList<SearchCriteria>();
        if (search != null) {
            Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),");
            Matcher matcher = pattern.matcher(search + ",");
            while (matcher.find()) {
                params.add(new SearchCriteria(, 
        return api.searchUser(params);

Note how we’re simply creating our search criteria objects out of the search expression.


We’re now at the point where we can start playing with the API and make sure everything is working correctly:



And here is its response:



7. Conclusion


This simple yet powerful implementation enables quite a bit of smart filtering on a REST API. Yes – it’s still rough around the edges and can be improved (and will be improved in the next article) – but it’s a solid starting point to implement this kind of filtering functionality on your APIs.

这个简单而强大的实现可以在REST API上实现相当多的智能过滤功能。是的–它的边缘还很粗糙,可以改进(在下一篇文章中会有改进)–但这是在你的API上实现这种过滤功能的一个坚实的起点。

The full implementation of this article can be found in the GitHub project.


Next »

REST Query Language with Spring Data JPA Specifications