Skip to content

6. Spring Data MongoDB y API REST

As we know, the Spring Data project, included in the Spring platform, provides a framework to simplify data access and persistence over different information repositories. Within this project is Spring Data MongoDB, which provides integration with MongoDB databases, through a model centered on POJOs that interact with document collections and provides a data access repository.

In this section, and taking over from the previous unit, we are going to address the development of data access components through Spring Data, as well as microservices that offer this data through a REST API, all of this, following the MVC pattern that we already know.

6.1. Defining the Model – Document

A MongoDB database is made up of Document collections. Although these Documents may have different structures from each other or different types of data, the model does require a static structure. So, the first thing we must do is create a class that represents this Main Document for MongoDB, which will be the one returned by the queries that are made.

In this context, there are two main annotations that we will use:

  • @Document → to indicate that a class corresponds to a domain object (domain object) that can be mapped in the database to offer persistence. This annotation for MongoDB would be the equivalent of @Entity in JPA. If nothing is indicated, the name of the collection to be used will be interpreted as corresponding to the name of the class in lower case. So, if we have the class com.jaume.ad.Person, the collection person will be used. However, we can indicate the collection we are working with, either through the value or collection attributes, with the following syntax:
  • @Document(value="collection")
  • @Document("collection")
  • @Document(collection="collection")
  • @Id It is applied to a field, and it is used to indicate that the field will be used as an identifier. As we know, every document in MongoDB requires an identifier. If one is not provided, the controller will assign an ObjectID automatically. It is important to note that the types of data that we can use as identifiers can be both Strings or BigInteger, since Spring will take care of converting them to the ObjectID type.

Important

It exists an anotation @DocumentReference to relate Documents one inside other, for instance when we store in a class objects from another classes, like relationships on SQL databases.

In addition to these, there are other more specific annotations that we can use. If you wish, you can consult them in the Spring Data MongoDB reference documentation here.

6.2. Defining the Repository

As we know, the repository is the interface in charge of managing access to data. In the case of MongoDB, this will derive from MongoRepository, which will be an interface parameterized by two arguments:

  • MongoRepository<T, Id>, who:
  • T → The type of document, which will correspond to the class defined in the model, and
  • Id→ The data type to which the identifier will belong.

The MongoRepository interface, as we have said, will be specific to MongoDB, and will derive from the CrudRepository and PagingAndSortingRepository interfaces, from which it will inherit all its methods. In this way, in the repository we will only have to declare those methods that are more specific for our application, since all the methods to implement CRUD operations, as well as findAll() and findById() will be inherited from MongoRepository.

To define our own queries in the repository, we will use the @Query annotation, providing the query in question as a value:

Java
@Query(value="{ parameterized_query}")   // regarding to repository's base class 
List<DocumentType> methodName(list_parameters);

To supply parameters to the query, these are received as arguments to the method, and are referenced by their order in the query: ?0 for the first argument, ?1 for the second, etc. MAybe in newer version can be parameters in a nominal way, as :parameter_name

6.3. Defining the service

The services take care of the business layer of our application, and access the data through the repository, sending the results to the controller. These services, in general, are characterized by:

  • Use the @Service annotations, to indicate to Spring that a service is being implemented
  • On the one hand, the Service interface is usually defined and, on the other hand, the implementation is carried out through the ServiceImpl class.
  • The @Autowired annotation is used in references to repositories to link or inject the service in question with said repository.
  • Once it gets the data from the repository, it sends it data to the controller.

6.4. Defining the controller

Finally, we are left with the controller implementation, which we already know from Spring. Let's remember the main characteristics of this one:

  • Use the @RestController annotation at the class level to indicate that you are dealing with a REST controller
  • Use the @RequestMapping annotation at the class level to specify the base path for the service endpoints,
  • Use the @Autowired annotation in the properties that refer to the service, to inject it automatically,
  • Use the @GetMapping, @PostMapping, @PutMapping, @DeleteMapping annotations on the methods that will implement GET, POST, PUT or DELETE type requests, specifying their Endpoint.
  • Use the @PathVariable or @RequestParam or @RequestBody annotation on the arguments of the above methods to get the values from path, request or body.

2. Swagger

Until now, we are testing our api rest with Postman, but now we offer a tool that integrates with Spring and Tomcat in order to test our API quickly and easily.

Swagger (https://swagger.io) is very simple to start:

  1. Add dependencies.
  2. Add a configuration class, who scans our controllers to find what entry points are defined.
  3. Create automoticaly a User Interface, who show what entries are defined and tools to test

2.1. Dependency

You must add at your pom.xml:

XML
1
2
3
4
5
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
</dependency>

2.2. Configuration class

We need to create a class like this:

Java
package com.jaumeii.moviesapi.swagger;

import java.util.Collections;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.service.Contact;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;

import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket apiDocket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.jaumeii.moviesapi.controller"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(getApiInfo());
    }

    private ApiInfo getApiInfo() {
        return new ApiInfo(
                "Movies controller",
                "Tools for testing movies API",
                "1.0",
                "http://www.ieseljust.com",
                new Contact("admin", "https://ieseljust.com", "admin@ieseljust.com"),
                "LICENSE",
                "LICENSE URL",
                Collections.emptyList()
                );
    }
}

Two methods need to be implemented:

  • Docket apiDocket() → who createa a Docket. This Docket contains references to work and test our API
  • ApiInfo getApiInfo() → who create an ApiInfo, and its name tells, is basic information about what is this API created for.

Attention

In las sample we have our controllers defindd in com.jaumeii.moviesapi.controller, and it is all that we could do.

2.3. Testing

When our spring project starts, we could see the log information:

Text Only
2023-01-29 08:37:16.400  INFO 61951 --- [  restartedMain] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]

It means that we have a new entry point /v2/api-docs. If we test this entry point, we get something like:

JavaScript
  "swagger": "2.0",
  "info": {
    "description": "Tools for testing movies API",
    "version": "1.0",
    "title": "Movies controller",
    "termsOfService": "http://www.ieseljust.com",
    "contact": {
      "name": "admin",
      "url": "https://ieseljust.com",
      "email": "admin@ieseljust.com"
    },
    "license": {
      "name": "LICENSE",
      "url": "LICENSE URL"
    }
  },
  "host": "localhost:8080",
  "basePath": "/",
  "tags": [
    {
      "name": "movie-controller",
      "description": "Movie Controller"
    }
  ],
  "paths": {
    "/api/v1/movie/byNumDirectors/{how_many}": {
      "get": {
        "tags": [
          "movie-controller"
        ],
        "summary": "findByYear",
        "operationId": "findByYearUsingGET",
        "produces": [
          "*/*"
  ...

It is a json document who describes our API. But to work in a comfortable way, we can request /swagger-ui.html, and we get:

![swagger1](./img/Swagger-1.png){width=75%}

This UI page contains the basic information we set in getApiInfo() method. At the bottom we can see more element. If we open, we get:

![swagger2](./img/Swagger-2.png){width=75%}

We could see information about entry points and models that the API returns. Additionally, we could enter on each entry point to get information about parameters, and finally execute it.

3. Appendix. Solution to exercise about mongo queries

  1. Obtain all the productions that either premiered in 2015 or are of the series type.

Tip

We need to use $or operator, with a condition array.

JavaScript
db.movies.find({$or:[{year:2015}, {type:"series"}]})
  1. Get all the movies NOT released between the years 2000 and 2002.

Tip

We need to use $not operator, with a condition to deny.

JavaScript
db.movies.find({year:{$not: {$gte: 2000, $lte: 2002}}}).pretty();
  1. Get all movies for which the "directors" key is not defined

Tip

We need to use $exists operator or checking for a null field.

JavaScript
1
2
3
4
5
db.movies.find({directors:{$exists:false}})

or

db.movies.find({directors:null})
  1. Get the title of all movies that start with the string star wars, regardless uppercase.

Tip

It is interesting using regular expressions. Remembre to use i option to inlude a non case sensitive check.

JavaScript
1
2
3
4
5
db.movies.find({"title":/^star wars/i},{title:1})

or

db.movies.find({"title": {$regex:'^star wars', $options:'i'}},{title:1})
  1. Get the title of all movies that contain the comedy genre (Comedy)

Tip

We look in the genres array for a given genre

JavaScript
db.movies.find({genres:"Comedy"}, {genres:1})
  1. Show the title and genres of the movies that contain either the comedy genre (Comedy) or adventure (Adventure).

Tip

We need to prove that film math $all given genres.

JavaScript
1
2
3
db.movies.find(
  {genres:{ $all: ["Comedy", "Adventure"]}},
  {title:1, genres:1})
  1. Get the title and genres of movies that have three genres.

Tip

We look for a genres array with three genres: $size operator.

JavaScript
db.movies.find({ genres : {$size:3} }, {title: 1, genres:1})
  1. Get the movies whose Rotten Tomatoes rating is higher than 4

Tip

We need to check into embedded documents, via dot notation inside a String :::

JavaScript
db.movies.find({"tomatoes.viewer.rating":{$gt:4}})
  1. Make the same query as before, but limiting the number of documents to 10.

Tip

We need to limit results.

JavaScript
db.movies.find({"tomatoes.viewer.rating":{$gt:4}}).limit(10)
  1. Now shows the title and rating of those movies with more than a 4 rating, ordered by rating (highest to lowest) and limiting the results to 10.

Tip

And now adding an ordering filter

JavaScript
1
2
3
4
5
db.movies.find(
    {"tomatoes.viewer.rating":{$gt:4}},
    {title:1, "tomatoes.viewer.rating":1})
    .sort({"tomatoes.viewer.rating":-1})
    .limit(10)