Spring Boot and MongoTemplate Tutorial with MongoDB

In this tutorial, we will build a Spring Boot application which demonstrates how to access data in a MongoDB database using the MongoTemplate APIs.  

For MongoDB, we will use mLab, which provides MongoDB Database as a Service platform so that you don’t even have to install a MongoDB database on your computer. 

Also, at the end of this tutorial, you will find a list of video courses that teach MongoDB. If you are making MongoDB a database of your project then I suggest you check those video courses because one of them might help you take your skills to a whole new level.

Setting up

To quickly set up our project, we will use a tool called Spring Initializr. Using this tool, we can quickly provide a list of Dependencies we need and download the bootstrapped application:

When creating a new Spring Boot project with Spring Initializr select only two dependencies:

  • Web (Spring Boot Starter Web)
  • MongoDB (MongoDB Starter)

Maven Dependencies

When you download the generated with Spring Initializr project and open its pom.xml file, you should see the below dependencies added:

<dependencies>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-mongodb</artifactId>
  </dependency>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
     <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
     </plugin>
  </plugins>
</build>

Project Structure

Before we move on and start working on the code for the project, let’s present here the project structure we will have once we’re finished adding all code to the project:

The code is organized in multiple packages so that the principle of separation of concern is followed and code remains modular.

Creating a Database

For MongoDB, we will be using mLab. You can create a free account with mLab and use MongoDB in the cloud without downloading and installing it on your computer.

When signed-in to mLab you will see a similar dashboard:

To create a new MongoDB database click on Create new button:

Once we have entered all details, we can confirm:

Once this is done, we will see a connection String appear like the one in the picture below:

Before we can start using this database, we need to create a user as well. Let’s do this on Users tab shown in above image as well:


Now we’re ready to move on and write some Java code as our mLab database is completely ready.

Application Configuration

With Spring Boot, it is fairly easy to configure our application with just a single mandatory property of MongoDB connection String:

# application properties
server.port=8090

# MongoDB properties
spring.data.mongodb.uri=mongodb://appsdeveloperblog:[email protected]:29670/appsdeveloperblog_db

We just provided a MongoDB connection String which will be read by Spring Boot and connection will be made using internal APIs.

Creating the model

We will make a simple Person entity with some fields with which we will be able to demonstrate simple MongoDB queries:

package com.appsdeveloperblog.mongotemplatedemo.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import static java.util.Calendar.DATE;
import static java.util.Calendar.MONTH;
import static java.util.Calendar.YEAR;

@Document(collection = "person")
public class Person {

   @Id
   private String personId;
   private String name;
   private long age;
   private List<String> favoriteBooks;
   private Date dateOfBirth;

   public Person() {
   }

   public Person(String name, List<String> childrenName, Date dateOfBirth) {
       this.name = name;
       this.favoriteBooks = childrenName;
       this.dateOfBirth = dateOfBirth;
       this.age = getDiffYears(dateOfBirth, new Date());
   }

   // standard getters and setters

   private int getDiffYears(Date first, Date last) {
       Calendar a = getCalendar(first);
       Calendar b = getCalendar(last);
       int diff = b.get(YEAR) - a.get(YEAR);
       if (a.get(MONTH) > b.get(MONTH) ||
               (a.get(MONTH) == b.get(MONTH) && a.get(DATE) > b.get(DATE))) {
           diff--;
       }
       return diff;
   }

   private Calendar getCalendar(Date date) {
       Calendar cal = Calendar.getInstance(Locale.US);
       cal.setTime(date);
       return cal;
   }

   @Override
   public String toString() {
       return String.format("Person{personId='%s', name='%s', age=%d, dateOfBirth=%s}\n",
               personId, name, age, dateOfBirth);
   }
}

Apart from simple fields, we have also added some helper functions which calculate the age of a user when we save it with his date of birth. This allows us to not have to calculate the age of user yourselves.

Defining Data Access Layer interface

Let’s define a data layer interface which will inform us how many operations we will demonstrate in our application. Here is the interface:

public interface PersonDAL {
   Person savePerson(Person person);
   List<Person> getAllPerson();
   List<Person> getAllPersonPaginated(
      int pageNumber, int pageSize);
   Person findOneByName(String name);
   List<Person> findByName(String name);
   List<Person> findByBirthDateAfter(Date date);
   List<Person> findByAgeRange(int lowerBound, int upperBound);
   List<Person> findByFavoriteBooks(String favoriteBook);
   void updateMultiplePersonAge();
   Person updateOnePerson(Person person);
   void deletePerson(Person person);
}

These are quite a few operations. The real fun is when we implement these operations which we will do next.

Implementing Data Access Layer

We will make use of MongoTemplate bean which is initialized by Spring Boot using the properties we defined in the application.properties above. Let’s see how we defined the needed beans:

@Repository
public class PersonDALImpl implements PersonDAL {

    private final MongoTemplate mongoTemplate;

    @Autowired
    public PersonDALImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    ...
}

We will start understanding the queries with simple methods first like to save and get all persons from the database:

@Override
public Person savePerson(Person person) {
   mongoTemplate.save(person);
   return person;
}

@Override
public List<Person> getAllPerson() {
   return mongoTemplate.findAll(Person.class);
}

MongoTemplate provides us some abstracted methods with which we can save an object into the database and get all data from Database as well.

Using Paginated Queries

The issue with the above method of getting all persons from the DB is that, there can be thousands of objects in the database. We should always implement pagination in our queries so that we can be sure that only limited data will be extracted from the database:

@Override
public List<Person> getAllPersonPaginated(int pageNumber, int pageSize) {
   Query query = new Query();
   query.skip(pageNumber * pageSize);
   query.limit(pageSize);

   return mongoTemplate.find(query, Person.class);
}

This way, only the pageSize number of objects will be fetched from the database at a time.

Getting Object by exact values

We can extract objects by matching exact values in the database as well:

@Override
public Person findOneByName(String name) {
   Query query = new Query();
   query.addCriteria(Criteria.where("name").is(name));

   return mongoTemplate.findOne(query, Person.class);
}

@Override
public List<Person> findByName(String name) {
   Query query = new Query();
   query.addCriteria(Criteria.where("name").is(name));

   return mongoTemplate.find(query, Person.class);
}

We showed two ways. First one obtained a single object from DB but the second method got all objects from DB with a matching condition.

Finding by Range and in List of Data

We can also find objects which have values of a field in a specified range. Or objects which have date data after a certain date. Let’s see how this can be done and how queries are constructed for the same:

@Override
public List<Person> findByBirthDateAfter(Date date) {
   Query query = new Query();
   query.addCriteria(Criteria.where("dateOfBirth").gt(date));

   return mongoTemplate.find(query, Person.class);
}

@Override
public List<Person> findByAgeRange(int lowerBound, int upperBound) {
   Query query = new Query();
   query.addCriteria(Criteria.where("age").gt(lowerBound)
           .andOperator(Criteria.where("age").lt(upperBound)));

   return mongoTemplate.find(query, Person.class);
}

@Override
public List<Person> findByFavoriteBooks(String favoriteBook) {
   Query query = new Query();
   query.addCriteria(Criteria.where("favoriteBooks").in(favoriteBook));
   return mongoTemplate.find(query, Person.class);
}

Updating an Object

We can update data in MongoDB with Update queries. We can find an object and then update the provided fields itself:

@Override
public void updateMultiplePersonAge() {

   Query query = new Query();
   Update update = new Update().inc("age", 1);
   mongoTemplate.findAndModify(query, update, Person.class);;
}

@Override
public Person updateOnePerson(Person person) {
   mongoTemplate.save(person);
   return person;
}

In the first query, we received all objects as no Criteria was added to the query. Next, we provided an Update clause in which we incremented the age of all users by one.

Deleting an Object

Deleting an Object is a matter of single method call as well:

@Override
public void deletePerson(Person person) {
   mongoTemplate.remove(person);
}

We could have simply passed the Query object as well with an ID of the person to be deleted as well.

Making a Command Line runner

We will run our application with a command-line runner in place which will some of the functions we defined in the above Data access layer implementation. Here is the Command-line runner:

package com.appsdeveloperblog.mongotemplatedemo;

import com.appsdeveloperblog.mongotemplatedemo.dal.PersonDAL;
import com.appsdeveloperblog.mongotemplatedemo.model.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Arrays;
import java.util.Date;

@SpringBootApplication
public class MongoTemplateApp implements CommandLineRunner {

  private static final Logger LOG = LoggerFactory.getLogger("AppsDeveloperBlog");

  private final PersonDAL personDAL;

  @Autowired
  public MongoTemplateApp(PersonDAL personDAL) {
     this.personDAL = personDAL;
  }

  public static void main(String[] args) {
     SpringApplication.run(MongoTemplateApp.class, args);
  }

  @Override
  public void run(String... args) {

     personDAL.savePerson(new Person(
           "Shubham", Arrays.asList("Harry potter", "Waking Up"), new Date(769372200000L)));
     personDAL.savePerson(new Person(
           "Sergey", Arrays.asList("Startup Guides", "Java"), new Date(664309800000L)));
     personDAL.savePerson(new Person(
           "David", Arrays.asList("Harry potter", "Success"), new Date(695845800000L)));
     personDAL.savePerson(new Person(
           "Ivan", Arrays.asList("Secrets of Butene", "Meeting Success"), new Date(569615400000L)));
     personDAL.savePerson(new Person(
           "Sergey", Arrays.asList("Harry potter", "Startup Guides"), new Date(348777000000L)));

     LOG.info("Getting all data from MongoDB: \n{}",
           personDAL.getAllPerson());

     LOG.info("Getting paginated data from MongoDB: \n{}",
           personDAL.getAllPersonPaginated(0, 2));

     LOG.info("Getting person By name 'Sergey': {}",
           personDAL.findByName("Sergey"));

     LOG.info("Getting all person By name 'Sergey': {}",
           personDAL.findOneByName("Sergey"));

     LOG.info("Getting people between age 22 & 26: {}",
           personDAL.findByAgeRange(22, 26));

  }
}

We can run our application using a simple command:

mvn spring-boot:run

Once we run the application, we will be able to see a simple output in our terminal:


Conclusion

If we compare the MongoTemplate with simple Spring Data JPA, it might look complex but it also gives us much more control about how to construct our queries.

Spring Data JPA abstracts away too much of the details about what queries are constructed, what criteria are passed to the query etc. With MongoTemplate, we have much granular control of the query composition and Spring Boot provides us with the ease of the application development.