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
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Manage Hierarchical Data in MongoDB With Spring
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • The Generic Way To Convert Between Java and PostgreSQL Enums
  • Spring Data: Easy MongoDB Migration Using Mongock

Trending

  • Manual Sharding in PostgreSQL: A Step-by-Step Implementation Guide
  • How To Develop a Truly Performant Mobile Application in 2025: A Case for Android
  • Agile and Quality Engineering: A Holistic Perspective
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 1
  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 user avatar
Anicet Eric
·
Jan. 29, 21 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
43.5K Views

Join the DZone community and get the full member experience.

Join For Free

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" : "Etiam.ligula.tortor@vestibulumMauris.com",
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" : "lobortis.ultrices.Vivamus@diamvelarcu.org",
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" : "Cras.dictum.ultricies@Integeridmagna.edu",
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" : "feugiat.metus@Duisa.edu",
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" : "mus.Proin@mollisvitaeposuere.net",
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" : "eu.placerat.eget@lacuspedesagittis.net",
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" : "velit.dui.semper@magnaNam.org",
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" : "risus.Donec.egestas@loremvitaeodio.edu",
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" : "lacus.Nulla@quistristique.org",
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" : "metus.Aenean@urnaNullamlobortis.edu",
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" : "fames.ac.turpis@dolor.edu",
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
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • The Generic Way To Convert Between Java and PostgreSQL Enums
  • Spring Data: Easy MongoDB Migration Using Mongock

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

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 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!