DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Manage Hierarchical Data in MongoDB With Spring
  • The Generic Way To Convert Between Java and PostgreSQL Enums
  • Spring Data: Easy MongoDB Migration Using Mongock
  • The Bill You Didn't See Coming

Trending

  • Spring Security JWT Validation Best Practices
  • Idempotency Under Concurrency: Why Your Application Check Isn't the Last Line of Defense
  • Beyond Manual Mapping: AI-Orchestrated Governance for Hybrid Cloud
  • How to Hide Sensitive Information Before You Share a File

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Data Engineering
  3. Databases
  4. Advanced Search and Filtering API Using Spring Data and MongoDB

Advanced Search and Filtering API Using Spring Data and MongoDB

In this tutorial, you will learn to implement filter/search Rest API for an existing Spring Boot application using Spring Data JPA and MongoDB.

By 
Anicet Eric
·
Jan. 29, 21 · Tutorial
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
45.2K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

It is common to have to perform complex searches in our API in production mode. Too much duplicate code has to be written to perform simple queries on each document.  

Spring Data offers the ability to perform simple or complex queries on MongoDB documents. 

In this tutorial, we will focus on dynamically building search and paging queries in MongoDB.

Prerequisites

  • Spring Boot 2.4
  • Maven 3.6.+
  • Java 8+
  • Mongo 4.4

Getting Started

We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Web, MongoDB, and Lombok.

Project Structure

Here is our project structure: 

To get started, we need a model class. For this tutorial, we have an Employeemodel class in which the Department class is embedded.

Java
 




x
10


 
1
@Data
2
@AllArgsConstructor
3
@NoArgsConstructor
4
public class Department {
5

          
6
    @NonNull
7
    private String code;
8

          
9
    private String name;
10
}




Java
 




xxxxxxxxxx
1
20


 
1
@Builder
2
@Data
3
@AllArgsConstructor
4
@NoArgsConstructor
5
@Accessors(chain = true)
6
@Document(collection = "employee")
7
public class Employee {
8

           
9
    @Id
10
    private String id;
11

           
12
    private String firstName;
13

           
14
    private String lastName;
15

           
16
    private String email;
17

           
18
    @NonNull
19
    private Department department;
20
}



We now have the common ResourceRepository interface which extends from MongoRepository and will include all custom methods.

Java
 




xxxxxxxxxx
1


 
1
@NoRepositoryBean
2
public interface ResourceRepository<T, I extends Serializable> extends MongoRepository<T, I> {
3

          
4
    Page<T> findAll(Query query, Pageable pageable);
5

          
6
    List<T> findAll(Query query);
7
}


EmployeeRepository will be extended to ResourceRepository. This will allow it to inherit the implicit methods provided by Spring Mongo Repository and ResourceRepository.

The Criteria and Query classes provide a way to query MongoDB with Spring Data by centralizing typed queries, which helps us avoid syntax errors.

We have, therefore, created a generic class, GenericFilterCriteriaBuilder, which will take care of building all the requests before sending them to Spring Data.

Java
 




xxxxxxxxxx
1
80


 
1
/**
2
 * This class is used to build all the queries passed as parameters.
3
 * filterAndConditions (filter list for the AND operator)
4
 * filterOrConditions (filter list for the OR operator)
5
 */
6
public class GenericFilterCriteriaBuilder {
7

          
8
    private final List<FilterCondition> filterAndConditions;
9
    private final List<FilterCondition> filterOrConditions;
10

          
11
    private static final Map<String, Function<FilterCondition, Criteria>>
12
            FILTER_CRITERIA = new HashMap<>();
13

          
14
    // Create map of filter
15
    static {
16
        FILTER_CRITERIA.put("EQUAL", condition -> Criteria.where(condition.getField()).is(condition.getValue()));
17
        FILTER_CRITERIA.put("NOT_EQUAL", condition -> Criteria.where(condition.getField()).ne(condition.getValue()));
18
        FILTER_CRITERIA.put("GREATER_THAN", condition -> Criteria.where(condition.getField()).gt(condition.getValue()));
19
        FILTER_CRITERIA.put("GREATER_THAN_OR_EQUAL_TO", condition -> Criteria.where(condition.getField()).gte(condition.getValue()));
20
        FILTER_CRITERIA.put("LESS_THAN", condition -> Criteria.where(condition.getField()).lt(condition.getValue()));
21
        FILTER_CRITERIA.put("LESSTHAN_OR_EQUAL_TO", condition -> Criteria.where(condition.getField()).lte(condition.getValue()));
22
        FILTER_CRITERIA.put("CONTAINS", condition -> Criteria.where(condition.getField()).regex((String) condition.getValue()));
23
        FILTER_CRITERIA.put("JOIN", condition -> Criteria.where(condition.getField()).is(new ObjectId((String) condition.getValue())));
24
    }
25

          
26

          
27
    public GenericFilterCriteriaBuilder() {
28
        filterOrConditions = new ArrayList<>();
29
        filterAndConditions = new ArrayList<>();
30
    }
31

          
32
    public Query addCondition(List<FilterCondition> andConditions, List<FilterCondition> orConditions) {
33

          
34
        if (andConditions != null && !andConditions.isEmpty()) {
35
            filterAndConditions.addAll(andConditions);
36
        }
37
        if (orConditions != null && !orConditions.isEmpty()) {
38
            filterOrConditions.addAll(orConditions);
39
        }
40

          
41
        List<Criteria> criteriaAndClause = new ArrayList<>();
42
        List<Criteria> criteriaOrClause = new ArrayList<>();
43
        Criteria criteria = new Criteria();
44

          
45
        // build criteria
46
        filterAndConditions.stream().map(condition -> criteriaAndClause.add(buildCriteria(condition))).collect(Collectors.toList());
47
        filterOrConditions.stream().map(condition -> criteriaOrClause.add(buildCriteria(condition))).collect(Collectors.toList());
48

          
49

          
50
        if (!criteriaAndClause.isEmpty() && !criteriaOrClause.isEmpty()) {
51
            return new Query(criteria.andOperator(criteriaAndClause.toArray(new Criteria[0])).orOperator(criteriaOrClause.toArray(new Criteria[0])));
52
        } else if (!criteriaAndClause.isEmpty()) {
53
            return new Query(criteria.andOperator(criteriaAndClause.toArray(new Criteria[0])));
54
        } else if (!criteriaOrClause.isEmpty()) {
55
            return new Query(criteria.orOperator(criteriaOrClause.toArray(new Criteria[0])));
56
        } else {
57
            return new Query();
58
        }
59

          
60
    }
61

          
62

          
63
    /**
64
     * Build the predicate according to the request
65
     *
66
     * @param condition The condition of the filter requested by the query
67
     * @return {{@link Criteria}}
68
     */
69
    private Criteria buildCriteria(FilterCondition condition) {
70
        Function<FilterCondition, Criteria>
71
                function = FILTER_CRITERIA.get(condition.getOperator().name());
72

          
73
        if (function == null) {
74
            throw new IllegalArgumentException("Invalid function param type: ");
75
        }
76

          
77
        return function.apply(condition);
78
    }
79

          
80
}


Let's Test

Suppose we have the «employee» collection with all the documents like this:

JSON
 




x
142


 
1
/* 1 */
2
{
3
    "_id" : ObjectId("600f4997e3a11bc10091f786"),
4
    "firstName" : "Ferdinand",
5
    "lastName" : "Wynne",
6
    "email" : "[email protected]",
7
    "department" : {
8
        "code" : "IT",
9
        "name" : "IT department"
10
    },
11
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
12
}
13

          
14
/* 2 */
15
{
16
    "_id" : ObjectId("600f49a1a5bd0e51ceb6c2d3"),
17
    "firstName" : "Grant",
18
    "lastName" : "Quinlan",
19
    "email" : "[email protected]",
20
    "department" : {
21
        "code" : "IT",
22
        "name" : "IT department"
23
    },
24
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
25
}
26

          
27
/* 3 */
28
{
29
    "_id" : ObjectId("600f49b6ff49b4e466efb4c4"),
30
    "firstName" : "Brielle",
31
    "lastName" : "Hanae",
32
    "email" : "[email protected]",
33
    "department" : {
34
        "code" : "IT",
35
        "name" : "IT department"
36
    },
37
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
38
}
39

          
40
/* 4 */
41
{
42
    "_id" : ObjectId("600f49aee40e8fd42bbf3e8f"),
43
    "firstName" : "Morgan",
44
    "lastName" : "Ivory",
45
    "email" : "[email protected]",
46
    "department" : {
47
        "code" : "RAD",
48
        "name" : "research and development team"
49
    },
50
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
51
}
52

          
53
/* 5 */
54
{
55
    "_id" : ObjectId("600f49c16c3c8f51ff49d30e"),
56
    "firstName" : "Alexa",
57
    "lastName" : "Colorado",
58
    "email" : "[email protected]",
59
    "department" : {
60
        "code" : "RAD",
61
        "name" : "research and development team"
62
    },
63
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
64
}
65

          
66
/* 6 */
67
{
68
    "_id" : ObjectId("600f49cc2eabbc1a8b9b7ead"),
69
    "firstName" : "Mercedes",
70
    "lastName" : "Zeph",
71
    "email" : "[email protected]",
72
    "department" : {
73
        "code" : "RAD",
74
        "name" : "research and development team"
75
    },
76
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
77
}
78

          
79
/* 7 */
80
{
81
    "_id" : ObjectId("600f49d3b5d8765523b9a17e"),
82
    "firstName" : "Chancellor",
83
    "lastName" : "Myra",
84
    "email" : "[email protected]",
85
    "department" : {
86
        "code" : "RAD",
87
        "name" : "research and development team"
88
    },
89
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
90
}
91

          
92
/* 8 */
93
{
94
    "_id" : ObjectId("600f49ddc441d3b63d2f3f15"),
95
    "firstName" : "Leroy",
96
    "lastName" : "Dillon",
97
    "email" : "[email protected]",
98
    "department" : {
99
        "code" : "RAD",
100
        "name" : "research and development team"
101
    },
102
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
103
}
104

          
105
/* 9 */
106
{
107
    "_id" : ObjectId("600f49e6687e2ce48b81831a"),
108
    "firstName" : "Cole",
109
    "lastName" : "Xander",
110
    "email" : "[email protected]",
111
    "department" : {
112
        "code" : "RAD",
113
        "name" : "research and development team"
114
    },
115
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
116
}
117

          
118
/* 10 */
119
{
120
    "_id" : ObjectId("600f49efecb77b12d517b519"),
121
    "firstName" : "Eleanor",
122
    "lastName" : "Paul",
123
    "email" : "[email protected]",
124
    "department" : {
125
        "code" : "MK",
126
        "name" : "marketing"
127
    },
128
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
129
}
130

          
131
/* 11 */
132
{
133
    "_id" : ObjectId("600f49f9be0c3402ac8f7416"),
134
    "firstName" : "TaShya",
135
    "lastName" : "Stewart",
136
    "email" : "[email protected]",
137
    "department" : {
138
        "code" : "TS",
139
        "name" : "technical support team"
140
    },
141
    "_class" : "com.tutorial.springdatamongodbdynamicqueries.domain.Employee"
142
}


This is the structure of the server-side pagination result from the APIs:

For each paging endpoint we have the following Params:

  • page=0: page index (default value 0)
  • size=20: page size (default value 20)
  • filterAnd= : And filters conditions (e.g. lastName|eq|john) 
  • filterOr= : Or filters conditions (e.g. lastName|eq|john) 
  • orders= : filters Orders 

* replace | by the code %7C

  • Get all the employees of the IT department order by lastName:

  • Get all the employees of IT and TS (technical support team) order by lastName

  • Get the first five employees ordered by name:

  • Get all the employees whose email contains .edu:

And we're done.

In this post, we have learned how to implement a filter/search REST API in a Spring Boot application using Spring Data JPA and MongoDB.

Full source code can be found on GitHub. 

Spring Framework API Spring Data MongoDB Data (computing) Database

Opinions expressed by DZone contributors are their own.

Related

  • Manage Hierarchical Data in MongoDB With Spring
  • The Generic Way To Convert Between Java and PostgreSQL Enums
  • Spring Data: Easy MongoDB Migration Using Mongock
  • The Bill You Didn't See Coming

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook