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
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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

  • The Full-Stack Developer's Blind Spot: Why Data Cleansing Shouldn't Be an Afterthought
  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • Concourse CI/CD Pipeline: Webhook Triggers
  • Automatic Code Transformation With OpenRewrite
  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

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: