Spring Data: MongoDB Tutorial

Overview

Spring Data is an umbrella project which contains many submodules, each specific to a particular database. In this article, we'll be covering Spring Data MongoDB by building an application that stores and retrieves data from MongoDB, a document based NO-SQL database.

If you'd like to read more about Spring Data, we've covered it in detail in - Guide to Spring Data JPA.

MongoDB

MongoDB is a document-oriented NoSQL database that stores JSON-like documents with dynamic schemas. It is commonly used for high volume data storage.

Before moving forward, it would be good to know some of the NoSQL database terms. Please note that these terms are not exactly a one-to-one in comparison with relational SQL databases:

  • Database: It is a container for collections and can be thought as similar to an RDBMS database, which is a container for Tables.
  • Collection: It is equivalent to Tables in RDBMS, but unlike a collection it has a dynamic schema. A collection exists within a database.
  • Document: It is a single record in a MongoDB collection. It can be thought of as a row in RDBMS.
  • Field: A document has zero or more fields. It's like an RDBMS column having a key-value pair.

To setup MongoDB server on your local machine you can download the installable here according to your operating system. You can then also download a tool like Compass for a nice GUI to interact with your server.

Another option and the one which we will be using is MongoDB Atlas, which is a cloud database as a service. After you signup, login and build a cluster using the free tier:

MongoDB Atlas create cluster

MongoDB Atlas cluster overview

To connect to our cluster, we have to create a user:

MongoDB Atlas create user

Let's now create our database and collection:

MongoDB create database

We are now set to connect to our collection using our Spring application.

Spring Data MongoDB Project

Setup

The best way to start with a skeleton project is to visit Spring Initializr. Select your preferred version of Spring Boot and add the Web and MongoDB dependencies:

Spring Initializr

After this, generate it as a Maven project and you're all set!

Defining a Collection

First, let's define our collection Candidate model class:

@Document(collection = "candidate")
public class Candidate {  
    @Id
    private String id;

    private String name;

    private double exp;

    @Indexed(unique = true)
    private String email;

    // getters and setters
}

Now let's take a look at these annotations:

  • @Document: This marks the class as a domain object that will be persisted into the database. The default collection name that is used is the class name (first character lowercased). We can map to a different collection in the database by using the collection attribute of the annotation.
  • @Id: This marks the field used for identity purposes.
  • @Indexed(unique = true): This is applied to the field that will be indexed with a constraint of unique.

Defining Repository

We create a repository by making an interface:

public interface CandidateRepository extends MongoRepository<Candidate, String> {}  

CandidateRepository extends the MongoRepository interface and plugs in the datatype of the document that we are working with, i.e Candidate and String respectively.

This will give us access to all the CRUD operations around the MongoDB collection.

Connection Setup

To set up a proper connection, we need to define the connection properties in application.properties:

spring.data.mongodb.uri=mongodb+srv://<USERNAME>:<PASSWORD>@<ClUSTER-NAME>-<INSTANCE-ID>/<DATABASE-NAME>?retryWrites=true  

You can get these values directly from the MongoDB Atlas UI:

Atlas connection URL

Note: If your password contains special characters then it must be URL encoded.

By default, your cluster is secured not to take requests from any client IP. We need to allow our IP to be able to connect to this cluster via an IP Whitelist:

Atlas IP whitelist 1

Atlas IP whitelist 2

Defining the Controller

Now, let's use our repository in our CandidateController via the @Autowired annotation:

@RestController
@RequestMapping("/candidate")
public class CandidateController {

    @Autowired
    private CandidateRepository candidateRepository;

Simple CRUD Operations

Insert

Let's create a POST mapping that will insert data to our MongoDB:

@PostMapping
@ResponseStatus(code = HttpStatus.CREATED)
public Candidate add(@RequestBody Candidate candidate) {  
    return candidateRepository.save(candidate);
}

We used the save() method on the candidateRepository object. The Candidate object is captured by @RequestBody and is used directly in the save() method.

MongoDB insert 1

If we try to use the same email ID again, we will get a duplicate key error:

MongoDB insert 2

We can check our collection status in Atlas too:

MongoDB insert 3

Read

Lets's create a couple of GET mappings to fetch our records.

@GetMapping
public List<Candidate> getAll() {  
    return candidateRepository.findAll();
}

@GetMapping(value = "/{id}")
public Candidate getOne(@PathVariable String id) {  
    return candidateRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException());
}

findAll() will return all the records in our database, while the findById() method will return a single record based on the ID passed.

MongoDB insert

If the record is not present it throws a custom runtime exception. ResourceNotFoundException is a custom class that returns 404 status if it's thrown:

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {  
    public ResourceNotFoundException() {
    }
}

If you'd like to read more about this, we've covered it in detail in - Exception Handling in Spring.

Update

Now to update a particular record, we'll use a PUT mapping:

@PutMapping(value = "/{id}")
public Candidate update(@PathVariable String id, @RequestBody Candidate updatedCandidate) {  
    Candidate candidate = candidateRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException());
    candidate.setName(updatedCandidate.getName());
    candidate.setExp(updatedCandidate.getExp());
    candidate.setEmail(updatedCandidate.getEmail());
    return candidateRepository.save(candidate);
}

We first check whether the Candidate with the given id is present or not. If not, we return a 404 status, otherwise we update the whole object and save it using the save() method:

MongoDB update

Delete

Now, let's delete a particular record by uding the DELETE mapping:

@DeleteMapping(value = "/{id}")
@ResponseStatus(code = HttpStatus.ACCEPTED)
public void delete(@PathVariable String id) {  
    Candidate candidate = candidateRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException());
    candidateRepository.delete(candidate);
}

We used the delete() method on the candidateRepository to delete the entry:

MongoDB delete

Custom Query Methods

We can add some methods to our CandidateRepository to have some additional functionality based on our business requirements:

public interface CandidateRepository extends MongoRepository<Candidate, String> {

    Optional<Candidate> findByEmail(String email);

    List<Candidate> findByExpGreaterThanEqual(double exp);

    List<Candidate> findByExpBetween(double from, double to);
}

Above, we added search functionality based on email and experience. All we need to do is follow a naming convention laid down by Spring Data.

After the findBy() method we write the name of the attribute in camel case, followed by any other restriction that we may want to enforce. The arguments to the method should match the where clause expectation. Spring Data will create actual queries for you during the startup of the application by using this interface.

Lets's use this in our controller:

@GetMapping("/searchByEmail")
public Candidate searchByEmail(@RequestParam(name = "email") String email) {  
    return candidateRepository.findByEmail(email)
        .orElseThrow(() -> new ResourceNotFoundException());

}

@GetMapping("/searchByExp")
public List<Candidate> searchByExp(@RequestParam(name = "expFrom") Double expFrom, @RequestParam(name = "expTo", required = false) Double expTo) {  
    List<Candidate> result = new ArrayList<>();
    if (expTo != null) {
        result = candidateRepository.findByExpBetween(expFrom, expTo);
    } else {
        result = candidateRepository.findByExpGreaterThanEqual(expFrom);
    }
    return result;
}

MongoDB custom queries

Conclusion

In this article, we've covered how to use Spring Data MongoDB to connect to a MongoDB server. We first created a MongoDB server in the cloud using MongoDB Atlas and then used Spring Data to connect to it. After that, we performed some simple CRUD operation as well as wrote some custom queries.

As always, the code for the examples used in this article can be found on Github.