A logo showing the text blog.marcnuri.com
Español
Home»Java»Spring Data MongoDB: Custom repository implementation

Recent Posts

  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK
  • Connecting to an MCP Server from JavaScript using LangChain.js
  • The Future of Developer Tools: Adapting to Machine-Based Developers
  • Connecting to a Model Context Protocol (MCP) Server from Java using LangChain4j

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Spring Data MongoDB: Custom repository implementation

2018-03-09 in Java tagged Java / MongoDB / Spring Framework / Spring Boot / Spring Data / Testing by Marc Nuri | Last updated: 2021-03-19
Versión en Español

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.

1/* ... */
2@Document(collection = "books")
3public class Book {
4
5  @Id
6  private String id;
7  private String title;
8  private String isbn;
9  private List<String> authorNames;
10  private Date publishDate;
11  private List<String> subjects;
12  /* ... */
13}

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.

1/* ... */
2public interface BookRepository extends MongoRepository<Book, String> {
3
4  List<Book> findByTitleContainingOrderByTitle(String titleContains);
5
6}

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.

1/* ... */
2public class DynamicQuery {
3
4  private String authorNameLike;
5  private Date publishDateBefore;
6  private Date publishDateAfter;
7  private String subject;
8  /* ... */
9}

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.

1/* ... */
2public interface BookRepositoryCustom {
3
4  List<Book> query(DynamicQuery dynamicQuery);
5
6}

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.

1/* ... */
2public class BookRepositoryImpl implements BookRepositoryCustom {
3
4  private final MongoTemplate mongoTemplate;
5
6  @Autowired
7  public BookRepositoryImpl(MongoTemplate mongoTemplate) {
8    this.mongoTemplate = mongoTemplate;
9  }
10
11  @Override
12  public List<Book> query(DynamicQuery dynamicQuery) {
13    final Query query = new Query();
14    final List<Criteria> criteria = new ArrayList<>();
15    if(dynamicQuery.getAuthorNameLike() != null) {
16      criteria.add(Criteria.where("authorNames").regex(MongoRegexCreator.INSTANCE.toRegularExpression(
17        dynamicQuery.getAuthorNameLike(), Part.Type.CONTAINING
18      ), "i"));
19    }
20    if(dynamicQuery.getPublishDateBefore() != null) {
21      criteria.add(Criteria.where("publishDate").lte(dynamicQuery.getPublishDateBefore()));
22    }
23    if(dynamicQuery.getPublishDateAfter() != null) {
24      criteria.add(Criteria.where("publishDate").gte(dynamicQuery.getPublishDateAfter()));
25    }
26    if(dynamicQuery.getSubject() != null) {
27      criteria.add(Criteria.where("subjects").regex(MongoRegexCreator.INSTANCE.toRegularExpression(
28        dynamicQuery.getSubject(), Part.Type.SIMPLE_PROPERTY
29      ), "i"));
30    }
31    if(!criteria.isEmpty()) {
32      query.addCriteria(new Criteria().andOperator(criteria.toArray(new Criteria[criteria.size()])));
33    }
34    return mongoTemplate.find(query, Book.class);
35  }
36
37}

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 Criteria where 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:

1/* ... */
2public interface BookRepository extends MongoRepository<Book, String>, BookRepositoryCustom {
3/* ... */

Testing the custom repository

Testing repositories with an embedded MongoDB will be covered in another tutorial.

We are going to create a BookRepositoryTest class which will test the interface method auto-implemented by Spring and our custom query method.

1/* ... */
2@RunWith(SpringJUnit4ClassRunner.class)
3@ContextConfiguration(classes = {BookRepository.class})
4@EnableMongoRepositories()
5@Import(EmbeddedMongoConfiguration.class)
6public class BookRepositoryTest {
7/* ... */
8  @Test
9  public void findByTitleContainingOrderByTitle_existingTitle_shouldReturnList() {
10    // Given
11    // DB with default books
12    final String existingBookPartialTitle = "lean Code";
13
14    // When
15    final List<Book> books = bookRepository.findByTitleContainingOrderByTitle(existingBookPartialTitle);
16
17    // Then
18    final int expectedCount = 1;
19    Assert.assertEquals(expectedCount, books.size());
20    Assert.assertEquals(books.size(), books.stream().filter(
21      b -> b.getTitle().contains(existingBookPartialTitle)).count());
22  }
23/* ... */
24  @Test
25  public void query_combinedQuery_shouldReturnList() {
26    // Given
27    // DB with default books
28    final String authorName = "Laakmann McDow";
29    final Date dateAfter = Date.from(LocalDate.of(2011, 8, 22)
30      .atStartOfDay().atZone(ZoneId.of(GMT_ZONE_ID)).toInstant());
31    final Date dateBefore = Date.from(LocalDate.of(2011, 8, 22)
32      .atTime(LocalTime.MAX).atZone(ZoneId.of(GMT_ZONE_ID)).toInstant());
33    final String subject = "JOB HUNTING";
34    final DynamicQuery dynamicQuery = new DynamicQuery();
35    dynamicQuery.setAuthorNameLike(authorName);
36    dynamicQuery.setPublishDateAfter(dateAfter);
37    dynamicQuery.setPublishDateBefore(dateBefore);
38    dynamicQuery.setSubject(subject);
39
40    // When
41    final List<Book> books = bookRepository.query(dynamicQuery);
42
43    // Then
44    final int expectedCount = 1;
45    Assert.assertEquals(expectedCount, books.size());
46  }
47/* ... */
48}

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:

BookRepository is Proxy
BookRepository is 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.

You can find the full source code for this article at GitHub.

Spring-Data + MongoDB
Spring-Data + MongoDB
Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Comments in "Spring Data MongoDB: Custom repository implementation"

  • Avatar for Jagannadh
    Jagannadh
    2018-06-05 15:34
    Hi Marc,
    When I try this I am getting property not found exception on method query.

    So basically spring is trying to find property "query" on my bean T
    • Avatar for Marc Nuri
      Marc Nuri
      2018-06-07 04:49

      Hi,

      Could you please provide more information and the code that is giving you trouble.

      Please also check that you named your classes correctly as most of the problems usually come by not following naming conventions and Spring being unable to find implementation classes.


Post navigation
Java 8 Streams: Convert List into MapField injection is not recommended – Spring IOC
© 2007 - 2025 Marc Nuri