Introduction

Spring Data makes really quick and easy the process of working with data entities, offering a specific implementation for MongoDB. You can merely define queries by creating interfaces with methods following a naming convention or annotating them with @Query and Spring will automagically generate an implementation for you. Most of the times this is enough for simple CRUD and query operations and there is no need to define additional methods. This will allow you to get up and running really quickly avoiding to type boilerplate code.

However, there are many times where this is not enough and the repository interface will need additional non-standard methods and a custom implementation for them.

In this post we’ll see how to define such custom implementation for a book repository in MongoDB and how to write unit tests for this custom implementation.

Defining the Entity/Document

The first step will be to define the main MongoDB Document that will be retrieved in the queries. For the sake of this tutorial we are going to use a simple Book document with basic information about a book publication.

Book.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

/* ... */

@Document(collection="books")

publicclassBook{

@Id

privateStringid;

privateStringtitle;

privateStringisbn;

privateList<String>authorNames;

privateDate publishDate;

privateList<String>subjects;

/* ... */

}

The main parts of this class are the @Document annotation that indicates that this class represents a MongoDB Document of the books collection, and the @Id annotation which tells Spring that this field should be used as the unique identification for the document.

As already stated, this entity will hold basic information about a book publication, such as title, ISBN, author names, etc.

Defining the base Repository

Next step is defining the base interface for the Book repository.

BookRepository.java

Java

1

2

3

4

5

6

/* ... */

publicinterfaceBookRepository extendsMongoRepository<Book,String>{

List<Book>findByTitleContainingOrderByTitle(StringtitleContains);

}

The repository extends MongoRepository interface, indicating Spring that this is a MongoDB specific repository and inheriting all of the methods available in the parent interfaces (PagingAndSortingRepository, CrudRepository…)

We are going to add a simple interface method findByTitleContainingOrderByTitle to highlight how Spring will auto-implement this method.

Defining the custom Repository methods

For this tutorial we want to add the capability to perform dynamic queries on the Book collection. For this purpose we are going to define a DynamicQuery class that will have optional fields for which we will be able to filter the collection.

DynamicQuery.java

Java

1

2

3

4

5

6

7

8

9

/* ... */

publicclassDynamicQuery{

privateStringauthorNameLike;

privateDate publishDateBefore;

privateDate publishDateAfter;

privateStringsubject;

/* ... */

}

Next we define an interface with a method that will consume a DynamicQuery and will return a list of Books. This interface will be named BookRepositoryCustom, It’s really important to follow the naming convention so that Spring will be able to instantiate the specific implementation for this new interface.

BookRepositoryCustom.java

Java

1

2

3

4

5

6

/* ... */

publicinterfaceBookRepositoryCustom{

List<Book>query(DynamicQuery dynamicQuery);

}

Implementing the custom Repository methods

As already stated, it’s important to follow the naming convention if we want Spring to detect our customized implementations. By default Spring will scan for a class below the package the repository is declared in. If the naming convention hasn’t been changed in the configuration, it will search for a class with an ‘Impl’ postfix in the name. We will create a BookRepositoryImpl implementing the BookRepositoryCustom interface.

This class will act as a regular Bean so we can autowire dependencies. In this case we are going to need a MongoTemplate to build our dynamic query, so we are using constructor based injection to autowire an instance.

As you can see, the class implements the query method and uses the autowired MongoTemplate to build a Criteria based query.

The method starts by crating an empty list of Criterias and checks if any of the fields inside the provided DynamicQuery is defined. For each of the defined fields it adds a new Criteriawhere clause. Next, the method merges all the Criterias in the list using andOperator and adds them to the Query. Finally the method runs the query in MongoTemplate for the collection specified in the Book class.

You can also see how to use regex filtering to query a field (subjects) ignoring case or if an array (authorNames) contains a given string.

Modify the initial repository to extend the custom interface

Finally we have to modify our initial BookRepository to extend the custom interface:

The first method in the snippet (findByTitleContainingOrderByTitle_existingTitle_shouldReturnList) checks that the auto implemented findByTitleContainingOrderByTitle works as expected by providing a partial title with case matching.

The second method in the snippet (query_combinedQuery_shouldReturnList) checks that our custom query implementation works for a DynamicQuery with several fields.

From a lower level perspective we can see that the injected object for our declared BookRepository interface is just a Proxy:

Depending on the method we call, the proxy will call one implementation or the other (i.e. SimpleMongoRepository or BookRepositoryImpl).

Conclusion

In this post we’ve seen how to add custom methods to a Spring-Data repository for MongoDB and how to implement them.

One of the key things to remember if you are not going to provide customized configuration is to follow the naming conventions for your custom interfaces and implementations.