In this tutorial, we will build a Spring Boot application that demonstrates how to use the MongoTemplate API to access data in the MongoDB database.
For MongoDB, we will use mLab , it provides the MongoDB database as a service platform, so you don't even have to install the MongoDB database on your computer.
to configure
To quickly set up our project, we will use a called Spring Initializr Tools. Using this tool, we can quickly provide the required dependency list and download the bootstrapper:
When creating a new Spring Boot project using Spring Initializr, only two dependencies are selected:
- Web (Spring Boot Starter Web)
- MongoDB (MongoDB Starter)
Maven Dependencies
When you download the project generated using Spring Initializr and open its pom.xml file, you should see the following 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 project's code, let's describe the project structure we will have after all the code is added to the project:
The code is organized in multiple packages, so it follows the principle of separation of concerns, and the code remains modular.
Create database
For MongoDB, we will use mLab . You can use mLab to create a free account and use MongoDB in the cloud without downloading and installing it on your computer.
After logging into mLab, you will see a similar dashboard:
To create a new MongoDB database, click the create new button:
After entering all the details, we can confirm that:
After this operation, we will see a connection string, as shown in the following figure:
Before we start using this database, we also need to create a user. Let's do this on the "user" tab shown above:
Now, we are ready to continue writing some Java code because our mLab database is fully ready.
Application configuration
Using Spring Boot, we can easily configure our application by using only a single required attribute of MongoDB connection String:
# application properties server.port=8090 # MongoDB properties spring.data.mongodb.uri=mongodb://cicoding.cn:password@ds915721.mlab.com:29670/cicoding_db
We just provided a MongoDB connection string, which will be read by Spring Boot, and the connection will be established using the internal API.
Model building
We will create a simple Person entity with some fields, which we will use to demonstrate a simple MongoDB query:
package com.cicoding.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); } }
In addition to simple fields, we have also added some helper functions to calculate the user's age when saving the user's birth date. This eliminates the need to calculate the user's age.
Define data access layer interfaces
Let's define a data layer interface that will tell us how many operations will be demonstrated in the application. This 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 number of operations. The real fun is what we will do next when we do these operations.
Implement data access layer
We will use the MongoTemplate bean, which is initialized by Spring Boot using the properties defined above in application.properties. Let's see how to define the required beans:
@Repository public class PersonDALImpl implements PersonDAL { private final MongoTemplate mongoTemplate; @Autowired public PersonDALImpl(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } ... }
We will begin to understand the query in a simple way. First, we need to save and get all personnel 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 with some abstract methods. Through these methods, we can save objects to the database or get all data from the database.
Using paged queries
The problem with the above method of getting everyone from the database is that there may be thousands of objects in the database. We should always implement paging in queries so that we can ensure that only limited data is 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); }
In this way, only pageSize objects will be obtained from the database at a time.
Get object by exact value
We can also extract objects by matching the exact values in the database:
@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 show two methods. The first method is to get a single object from the database, while the second method is to get all objects with matching conditions from the database.
Find by range and data list
We can also find objects with field values within the specified range. Or an object with date data after a specific date. Let's see how to do this and how to construct the same query:
@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); }
Update Object
We can use the Update query to Update the data in MongoDB. We can find an object and Update the provided fields ourselves:
@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, since no criteria were added to the query, we received all objects. Next, we provide an Update clause in which the age of all users is increased by one.
delete object
Deleting objects is also related to a single method call:
@Override public void deletePerson(Person person) { mongoTemplate.remove(person); }
We can also simply pass the Query object and the ID of the person to delete.
Make command line runner
We will run our application in the appropriate place using the command line runner, which will provide some of the functions we defined in the data access layer implementation above. This is the command line runner:
package com.cicoding.mongotemplatedemo; import com.cicoding.mongotemplatedemo.dal.PersonDAL; import com.cicoding.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("cicoding"); 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 with a simple command:
mvn spring-boot:run
After running the application, we will be able to see a simple output in the terminal:
conclusion
If we compare MongoTemplate with simple Spring Data JPA, it may look complex, but it also gives us more control over how to construct queries.
Spring Data JPA extracts too much detailed information about what query to construct, what conditions to pass to the query, etc. Using MongoTemplate, we can control the query composition more finely. Spring Boot provides us with an easy to develop application.