Getting started with Spring Data MongoDB

April 19, 2021 No comments Spring Boot MongoDB CRUD Spring Data

1. Introduction

MongoDB is a document-oriented NoSQL database that provides, high performance, high availability, and easy scalability. In Spring applications we could integrate with MongoDB using Spring Data dedicated interface. This tutorial will cover an introduction to Spring Data for MongoDB, we will present how to configure the environment to start working with that database and show common operations.

2. MongoDB configuration

Since Spring Boot can deduce the application's configuration from the JAR dependencies on the CLASSPATH all we need to do is to add entry (with spring-boot-starter-data-mongodb) in our pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.frontbackend.springboot</groupId>
    <artifactId>getting-started-with-mongodb</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4-SNAPSHOT</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

By default, the Spring Boot tries to connect to a MongoDB server at mongodb://localhost/test, we could change that and other settings as well in the application.properties file:

spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=mongoadmin
spring.data.mongodb.password=secret
spring.data.mongodb.database=test
spring.data.mongodb.port=27888
spring.data.mongodb.host=localhost

In case our MongoDB instance is secured we must specify authentication-database and password in order to log in properly.

3. Run MongoDB instance

The easiest way to start an instance of MongoDB database on our local environment is to use docker:

docker run -d  --name mongo-on-docker  -p 27888:27017 -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=secret mongo

Let me explain briefly used parameters:

  • docker run - command that runs the specified image and starts the docker container,
  • -d - parameter runs the container in background,
  • -name mongo-on-docker - defines a name for the container, so that we could easily found it,
  • -p 27888:27017 - the local port 27888 is mapped to the internal 27017 port (format local:internal),
  • -e MONGO_INITDB_ROOT_USERNAME=mongoadmin - root username,
  • -e MONGO_INITDB_ROOT_PASSWORD=secret - root password,
  • mongo - the name of the MongoDB image to run.

4. Model object

Let's start with sample model object that will become MongoDB document:

package com.frontbackend.springboot.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;

public class Snippet {

    @Id
    private String id;

    private String code;

    @Indexed
    private String language;

    public Snippet() {
    }

    public Snippet(String code, String language) {
        this.code = code;
        this.language = language;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }
}

As you can see this is a simple POJO class, in addition, we could use @Document annotation in case we want to name differently the collection in the Mongo database. By default, the collection will be named with a class name.

The @Id annotation is an object's unique identifier. If this value is empty Mongo will generate a random value after saving the object. If a value is set database will replace it or create new, depending on whether an object with the same id already exists in the Mongo or not.

The @Indexed annotation is used to support the efficient execution of queries in Mongo. If we want to use a field very often in search queries we should consider adding @Indexed annotation for that field.

5. Mongo Repository

To create a DAO object that will be responsible for CRUD operations on the MongoDB we will use MongoRepository:

package com.frontbackend.springboot.repository;

import com.frontbackend.springboot.model.Snippet;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SnippetRepository extends MongoRepository<Snippet, String> {
}

This example code shows how to create a repository with CRUD operations for the Snippet object.

6. Insert object in MongoDB

To save new object in the Mongo database we could use insert(...) or save(...) method available in MongoRepository interface:

Snippet snippet = new Snippet("alert('test')", "javascript");

Snippet saved = snippetRepository.insert(snippet);

In this case, Snippet id field will be filled with auto-generated random value after the insert(...) method.

7. Update inserted object

To update an object stored in the Mongo database we could simply use save(...) method but in this case updated object needs to have a filled identifier.

Snippet snippet = new Snippet("alert('test')", "javascript");

Snippet saved = snippetRepository.insert(snippet);
saved.setLanguage("es6");
snippetRepository.save(saved);

8. Remove object

The MongoRepository provides two methods to delete object from MongoDB: deleteById(...) and delete(...). The first one takes object identifier and the second one expects an object as argument:

Snippet snippet = new Snippet("alert('test')", "javascript");

Snippet saved = snippetRepository.save(snippet);
snippetRepository.deleteById(saved.getId());

9. Found object by id

We could find objects using their identifier:

Snippet snippet = new Snippet("alert('test')", "javascript");

Snippet saved = snippetRepository.save(snippet);

Optional<Snippet> byId = snippetRepository.findById(saved.getId());

10. Find all inserted objects

There is also a method that returns all inserted values:

Snippet snippet1 = new Snippet("alert('test')", "javascript");
Snippet snippet2 = new Snippet("() => alert('test')", "es6");

snippetRepository.save(snippet1);
snippetRepository.save(snippet2);

List<Snippet> all = snippetRepository.findAll();

11. Get the first page

We could use special interface Pageable to return just a single page of data:

Snippet snippet1 = new Snippet("alert('test')", "javascript");
Snippet snippet2 = new Snippet("() => alert('test')", "es6");

snippetRepository.save(snippet1);
snippetRepository.save(snippet2);

Pageable pageableRequest = PageRequest.of(0, 1); // first page, page size = 1
Page<Snippet> paged = snippetRepository.findAll(pageableRequest);

12. Check if an object exists in the database

Mongo repository allows checking if the object exists in the database:

 Snippet snippet = new Snippet("alert('test')", "javascript");

Snippet saved = snippetRepository.save(snippet);

snippetRepository.existsById(saved.getId());

13. Sorting collection

To sort values we could use findAll(...) method with given Sort instance:

Snippet snippet1 = new Snippet("alert('test')", "javascript");
Snippet snippet2 = new Snippet("() => alert('test')", "es6");

snippetRepository.save(snippet1);
snippetRepository.save(snippet2);

Sort sortBy = Sort.by(Sort.Direction.ASC, "language");
List<Snippet> sorted = snippetRepository.findAll(sortBy);

14. JUnit test with all test cases

The JUnit test class with all the above examples could look like the following:

package com.frontbackend.springboot.repository;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.frontbackend.springboot.model.Snippet;

@SpringBootTest
public class SnippetRepositoryTest {

    @Autowired
    private SnippetRepository snippetRepository;

    @BeforeEach
    public void setUp() {
        snippetRepository.deleteAll();
    }

    @Test
    public void shouldSaveSnippet() {
        Snippet snippet = new Snippet("alert('test')", "javascript");

        Snippet saved = snippetRepository.insert(snippet);

        System.out.println(saved);

        assertThat(saved).isNotNull();
        assertThat(saved.getId()).isNotNull();

        Optional<Snippet> snippetOptional = snippetRepository.findById(saved.getId());
        assertThat(snippetOptional).isPresent();
    }

    @Test
    public void shouldUpdateSnippet() {
        Snippet snippet = new Snippet("alert('test')", "javascript");

        Snippet saved = snippetRepository.save(snippet);
        saved.setLanguage("es6");
        snippetRepository.save(saved);

        Optional<Snippet> snippetOptional = snippetRepository.findById(saved.getId());
        assertThat(snippetOptional).isPresent();

        Snippet updated = snippetOptional.get();
        assertThat(updated.getLanguage()).isEqualTo("es6");
    }

    @Test
    public void shouldRemoveByIdSnippet() {
        Snippet snippet = new Snippet("alert('test')", "javascript");

        Snippet saved = snippetRepository.save(snippet);
        snippetRepository.deleteById(saved.getId());

        Optional<Snippet> byId = snippetRepository.findById(saved.getId());
        assertThat(byId).isNotPresent();
    }

    @Test
    public void shouldFoundById() {
        Snippet snippet = new Snippet("alert('test')", "javascript");

        Snippet saved = snippetRepository.save(snippet);

        Optional<Snippet> byId = snippetRepository.findById(saved.getId());
        assertThat(byId).isPresent();
    }

    @Test
    public void shouldFindAll() {
        Snippet snippet1 = new Snippet("alert('test')", "javascript");
        Snippet snippet2 = new Snippet("() => alert('test')", "es6");

        snippetRepository.save(snippet1);
        snippetRepository.save(snippet2);

        List<Snippet> all = snippetRepository.findAll();

        assertThat(all.size()).isEqualTo(2);
    }

    @Test
    public void shouldFindAllPageable() {
        Snippet snippet1 = new Snippet("alert('test')", "javascript");
        Snippet snippet2 = new Snippet("() => alert('test')", "es6");

        snippetRepository.save(snippet1);
        snippetRepository.save(snippet2);

        Pageable pageableRequest = PageRequest.of(0, 1); // first page, page size = 1
        Page<Snippet> paged = snippetRepository.findAll(pageableRequest);

        assertThat(paged.getTotalElements()).isEqualTo(2);
        assertThat(paged.getTotalPages()).isEqualTo(2);
        assertThat(paged.getContent()
                        .size()).isEqualTo(1);
    }

    @Test
    public void shouldExists() {
        Snippet snippet = new Snippet("alert('test')", "javascript");

        Snippet saved = snippetRepository.save(snippet);

        assertThat(snippetRepository.existsById(saved.getId())).isTrue();
    }

    @Test
    public void shouldSort() {
        Snippet snippet1 = new Snippet("alert('test')", "javascript");
        Snippet snippet2 = new Snippet("() => alert('test')", "es6");

        snippetRepository.save(snippet1);
        snippetRepository.save(snippet2);

        Sort sortBy = Sort.by(Sort.Direction.ASC, "language");
        List<Snippet> sorted = snippetRepository.findAll(sortBy);

        assertThat(sorted.get(0)
                         .getLanguage()).isEqualTo("es6");
    }
}

15. Conclusion

In this article, we presented how to configure, integrate and start using Spring Data for the Mongo database.

As usual, the code used in this article could be found on our GitHub.

{{ message }}

{{ 'Comments are closed.' | trans }}