Spring Boot + MongoDB CRUD Example Tutorial

In this tutorial, we will learn to integrate MongoDB with a Spring Boot Application and perform different CRUD operations (Create, Read, Update, and Delete operations) on the Employee entity.

Spring Boot CRUD Restful API with MongoDB Database

In this tutorial, we will also learn how to implement a sequential, auto-generated field for MongoDB in Spring Boot. When we're using MongoDB as the database for a Spring Boot application, we can't use @GeneratedValue annotation in our models as it's not available. Hence we need a method to produce the same effect as we'll have if we're using JPA and an SQL database.
MongoDB is a document database with the scalability and flexibility that you want with the querying and indexing that you need. 
Learn Spring boot at https://www.javaguides.net/p/spring-boot-tutorial.html.
Read more about MongoDB at the official website.

Video Tutorial

This tutorial also explained in below YouTube video:

Setup MongoDB

To work with MongoDB, you need to have MongoDB installed in your system. Check out the official MongoDB doc for installing MongoDB in your System.
You can also install the Zip version of MongoDB in Windows, check out this article at https://www.javaguides.net/2019/12/install-mongodb-zip-in-windows.html.

Tools and technologies used

  • Spring Boot - 2.1.0.RELEASE
  • Spring Framework - 5.1.2.RELEASE
  • Spring Data - 2.1.2.RELEASE
  • MongoDB - 3.8.2
  • JDK - 1.8 or later
  • Maven - 3.5.1
  • IDE - STS/Eclipse Neon.3
Let me list out the development steps so that it will be easy to develop and understand the Spring Boot Application with MongoDB step by step.

Development Steps

1. Create a Spring Boot Application
2. The pom.xml File - Define Maven Dependencies
3. Project Structure
4. Configuring MongoDB database
5. Creating the Employee Model
6. Create a Spring Data Repository - EmployeeRepository.java
8. Creating the APIs - EmployeeController
9. Exception handling in RESTFul web services
9. Running Spring boot application
10. Test REST APIs Using Postman Client

1. Create a Spring Boot Application

There are many ways to create a Spring Boot application. You can refer below articles to create a Spring Boot application.

2. The pom.xml File - Define Maven Dependencies

Let's define all the spring boot starter maven dependencies required to build this application in below 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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <groupId>net.springboot.javaguides</groupId>
    <artifactId>springboot-mongodb-crud-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-thymeleaf-web-app</name>
    <description>Demo project for Spring Boot + Mongo DB CRUD Example
 </description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3. Project Structure

Following is the packing structure for your reference -

4. Configuring MongoDB database

Spring Boot tries to auto-configure most of the stuff for you based on the dependencies that you have added in the pom.xml file.
Since we have added a spring-boot-starter-mongodb dependency, Spring Boot tries to build a connection with MongoDB by reading the database configuration from the application.properties file.
Open application.properties file and add the following MongoDB properties -
# MONGODB (MongoProperties)
spring.data.mongodb.uri=mongodb://localhost:27017/EmployeeDB
As per our configuration, Mongo DB is running locally at default port 27017.
Note that we need to create "EmployeeDB" database using the following command:
use EmployeeDB
If the "EmployeeDB" database is not present in MongoDB then it will create a new one.

5. Creating Model Classes

We will create a collection that'll store the auto-incremented sequence for other collections. We'll call this collection database_sequences. It can be created using either the mongo shell or MongoDB Compass. Let's create a corresponding model class:
package net.guides.springboot.crud.model;

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


@Document(collection = "database_sequences")
public class DatabaseSequence {

    @Id
    private String id;

    private long seq;

    public DatabaseSequence() {}

    public String getId() {
        return id;
    }

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

    public long getSeq() {
        return seq;
    }

    public void setSeq(long seq) {
        this.seq = seq;
    }
}


Let’s now create the Employee model which will be mapped to a Document in the MongoDB database. Create a new package model inside net.guides.springboot.crud(refer project structure section), and add a file Employee.java inside the model package with the following contents -
package net.guides.springboot.crud.model;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

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

@Document(collection = "Employee")
public class Employee {

    @Transient
    public static final String SEQUENCE_NAME = "users_sequence";

    @Id
    private long id;

    @NotBlank
    @Size(max = 100)
    @Indexed(unique = true)
    private String firstName;
    private String lastName;

    @NotBlank
    @Size(max = 100)
    @Indexed(unique = true)
    private String emailId;

    public Employee() {

    }

    public Employee(String firstName, String lastName, String emailId) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailId = emailId;
    }

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmailId() {
        return emailId;
    }
    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", emailId=" + emailId +
            "]";
    }
}
We have an annotated firstName and emailId with @Indexed annotation and marked it as unique. This creates a unique index on firstName and emailId fields.

6. Create a Spring Data Repository - EmployeeRepository.java

Next, we need to create EmployeeRepository for accessing data from the database.
package net.guides.springboot.crud.repository;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import net.guides.springboot.crud.model.Employee;

@Repository
public interface EmployeeRepository extends MongoRepository<Employee, Long>{

}

7. SequenceGeneratorService - Auto-Generate Sequence

package net.guides.springboot.crud.service;

import static org.springframework.data.mongodb.core.FindAndModifyOptions.options;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import net.guides.springboot.crud.model.DatabaseSequence;

@Service
public class SequenceGeneratorService {

    private MongoOperations mongoOperations;

    @Autowired
    public SequenceGeneratorService(MongoOperations mongoOperations) {
        this.mongoOperations = mongoOperations;
    }

    public long generateSequence(String seqName) {

        DatabaseSequence counter = mongoOperations.findAndModify(query(where("_id").is(seqName)),
                new Update().inc("seq",1), options().returnNew(true).upsert(true),
                DatabaseSequence.class);
        return !Objects.isNull(counter) ? counter.getSeq() : 1;

    }
}

8. Creating the APIs - EmployeeController

Now, let’s create the APIs which will be exposed to the clients. Notice the usage of annotations @RestController, @RequestMapping, @GetMapping, @PostMapping, and @DeleteMapping to map various URIs to controller methods. Note that we are using the generateSequence() while creating a new record:

package net.guides.springboot.crud.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import net.guides.springboot.crud.exception.ResourceNotFoundException;
import net.guides.springboot.crud.model.Employee;
import net.guides.springboot.crud.repository.EmployeeRepository;
import net.guides.springboot.crud.service.SequenceGeneratorService;

//@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private SequenceGeneratorService sequenceGeneratorService;

    @GetMapping("/employees")
    public List < Employee > getAllEmployees() {
        return employeeRepository.findAll();
    }

    @GetMapping("/employees/{id}")
    public ResponseEntity < Employee > getEmployeeById(@PathVariable(value = "id") Long employeeId)
    throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));
        return ResponseEntity.ok().body(employee);
    }

    @PostMapping("/employees")
    public Employee createEmployee(@Valid @RequestBody Employee employee) {
        employee.setId(sequenceGeneratorService.generateSequence(Employee.SEQUENCE_NAME));
        return employeeRepository.save(employee);
    }

    @PutMapping("/employees/{id}")
    public ResponseEntity < Employee > updateEmployee(@PathVariable(value = "id") Long employeeId,
        @Valid @RequestBody Employee employeeDetails) throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employee.setEmailId(employeeDetails.getEmailId());
        employee.setLastName(employeeDetails.getLastName());
        employee.setFirstName(employeeDetails.getFirstName());
        final Employee updatedEmployee = employeeRepository.save(employee);
        return ResponseEntity.ok(updatedEmployee);
    }

    @DeleteMapping("/employees/{id}")
    public Map < String, Boolean > deleteEmployee(@PathVariable(value = "id") Long employeeId)
    throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
            .orElseThrow(() - > new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employeeRepository.delete(employee);
        Map < String, Boolean > response = new HashMap < > ();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

9. Exception(Error) Handling for RESTful Services

Spring Boot provides a good default implementation for exception handling for RESTful Services. Let’s quickly look at the default Exception Handling features provided by Spring Boot.

Resource Not Present

Heres what happens when you fire a request to not resource found: http://localhost:8080/some-dummy-url
{
  "timestamp": 1512713804164,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/some-dummy-url"
}
That's a cool error response. It contains all the details that are typically needed.
In spring boot application, we can specify the Response Status for a specific exception along with the definition of the Exception with ‘@ResponseStatus’ annotation.

ResourceNotFoundException

Lets create a ResourceNotFoundException.java class.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

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

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message){
        super(message);
    }
}

Customizing Error Response Structure - ErrorDetails.java

Default error response provided by Spring Boot contains all the details that are typically needed. However, you might want to create a framework independent response structure for your organization. In that case, you can define a specific error response structure. Let’s define a simple error response bean.
import java.util.Date;

public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;

    public ErrorDetails(Date timestamp, String message, String details) {
        super();
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public String getMessage() {
        return message;
    }

    public String getDetails() {
        return details;
    }
}

GlobalExceptionHandler

To use ErrorDetails to return the error response, let’s create a GlobalExceptionHandler class annotated with @ControllerAdvice annotation. This class handles exception specific and global exceptions in a single place.
import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
    ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

10. Running Spring boot application

This spring boot application has an entry point Java class called Application.java with the public static void main(String[] args) method, which you can run to start the application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

11. Test REST APIs Using Postman Client

Create Employee Rest API - Post HTTP Request

Update Employee Rest API - PUT HTTP Request


Delete Employee Rest API - Delete HTTP Request

Get Employee Rest API - Get HTTP Request


Get All Employee Rest API - Get HTTP Request

Source code on GitHub 


Comments

  1. I am getting the error when i execute this project

    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    Mon Jul 20 23:19:40 IST 2020
    There was an unexpected error (type=Not Found, status=404).

    What is the solution to this error??

    ReplyDelete
  2. This tutorial is really very good and gives detailed information on each steps. Thanks a lot for this useful topic.

    ReplyDelete

Post a comment