BLOG

Acceso a datos con Spring Data a MongoDB
Programación-Tecnología

En este post aprenderemos a crear un microservicio spring boot que utilizará Spring Data MongoDB para crear una aplicación que almacenará y recuperará datos de MongoDB, una de las bases de datos NoSQL orientada a documentos más populares

Instalación y ejecución de MongoDB

Si no dispones de una instalación previa de MongoDB, en este link tienes información de cómo hacerlo.

Para instalar por ejemplo MongoDB en Mac OS X, ejecuta el siguiente comando:

$ brew install mongodb

Crea el directorio donde el proceso mongod escribirá los datos cuando MongoDB esté levantado. Es importarte otorgar permisos de lectura y escritura al usuario que ejecute el proceso:

$ mkdir -p /data/db
$ sudo chmod 777 /data/db

Por último levanta el servidor de MongoDB:

$ mongod

Por defecto las peticiones se escuchan en el puerto 27017

Antes de empezar

Si te resulta útil, puedes descargarte el el código fuente de este ejercicio desde mi proyecto de GitHub

La configuración del fichero pom.xml del proyecto es la siguiente:

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>net.robertocrespo.microservices</groupId>
	<artifactId>users</artifactId>
	<version>1.0.1-RELEASE</version>
	<packaging>jar</packaging>

	<name>users</name>
	<description>This project is in charge of users service</description>


	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.3.5.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<springfox.version>2.4.0</springfox.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter</artifactId>
			<version>1.1.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mongodb</groupId>
			<artifactId>mongo-java-driver</artifactId>
		</dependency>
		
		<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox.version}</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox.version}</version>
        </dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>

			<plugin>
				<artifactId>maven-release-plugin</artifactId>
				<version>2.5</version>
				<dependencies>
					<dependency>
						<groupId>org.apache.maven.scm</groupId>
						<artifactId>maven-scm-provider-gitexe</artifactId>
						<version>1.3</version>
					</dependency>
				</dependencies>
			</plugin>

		</plugins>
	</build>

Implementación del microservicio

1 – Crear objeto de dominio

Crearemos un POJO muy sencillo para representar un User, que será el que almacenemos en MongoDB

package net.robertocrespo.microservices.users.model;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document
import java.io.Serializable;
import javax.validation.constraints.NotNull;

@Document(collection = "users")
@JsonPropertyOrder({"userId", "name"})
public class User implements Serializable{

    private static final long serialVersionUID = -7788619177798333712L;

    @Id
    @NotNull  
    private String userId;
    @NotNull    
    private String name;
    
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}   
}

Utilizamos la anotación @Document para definir un nombre de una colección cuando el objeto se guarde en MongoDB. En este caso, cuando el objeto “user” se guarde, se hará dentro de la colección “users”.

La anotación de Jackson @JsonPropertyOrder nos permite especificar el orden en que los campos del objeto java deberían ser serializados en JSON.

2 – Crear User repository

Lo primero de todo será crear el interface del repositorio que permita realizar varias operaciones CRUD sobre el objeto User

package net.robertocrespo.microservices.users.repository;

import net.robertocrespo.microservices.users.model.User;

public interface UserRepository{

  Optional&lt;List&lt;User&gt;&gt; findAll();

  public User saveUser(User user);
  
  public void updateUser(User user);
  
  public void deleteUser(String userId);
}

Y a continuación la implementación del interface:

package net.robertocrespo.microservices.users.repository.impl;

import net.robertocrespo.microservices.users.repository.UserRepository;
import net.robertocrespo.microservices.users.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Optional;

import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

@Repository
public class UserRepositoryImpl implements UserRepository{

    private final MongoOperations mongoOperations;

    @Autowired
     @Autowired
    public UserRepositoryImpl(MongoOperations mongoOperations) {
        Assert.notNull(mongoOperations);
        this.mongoOperations = mongoOperations;
    }
    
    //Find all users
    public Optional&amp;lt;List&amp;lt;User&amp;gt;&amp;gt; findAll() {
    	List&amp;lt;User&amp;gt; users = this.mongoOperations.find(new Query(), User.class);
        Optional&amp;lt;List&amp;lt;User&amp;gt;&amp;gt; optionalUsers = Optional.ofNullable(users);
        return optionalUsers;
	}    

    public Optional&amp;lt;User&amp;gt; findOne(String userId) {
        User d = this.mongoOperations.findOne(new Query(Criteria.where("userId").is(userId)), User.class);
        Optional&amp;lt;User&amp;gt; user = Optional.ofNullable(d);
        return user;
    }

    public User saveUser(User user) {
        this.mongoOperations.save(user);
        return findOne(user.getUserId()).get();
    }
    
    public void updateUser(User user) {
        this.mongoOperations.save(user);
    }

    public void deleteUser(String userId) {
        this.mongoOperations.findAndRemove(new Query(Criteria.where("userId").is(userId)), User.class);
    }
}
3 – Implementar User Service

Lo siguiente que haremos será crear el servicio (interface + implementación) que se conecte al repositorio de usuarios del paso anterior:

package net.robertocrespo.microservices.users.service;

import net.robertocrespo.microservices.users.model.User;
import java.util.List;

public interface UserService {
	
  List&amp;amp;lt;User&amp;amp;gt; findAll();
	
  User findByUserId(String userId);
  
  User saveUser(User user);

  void updateUser(User user);

  void deleteUser(String userId);
}

A continuación la implementación de la interface UserService:

package net.robertocrespo.microservices.users.service.impl;

import net.robertocrespo.microservices.users.exception.UserNotFoundException;
import net.robertocrespo.microservices.users.model.User;
import net.robertocrespo.microservices.users.repository.UserRepository;
import net.robertocrespo.microservices.users.service.UserService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {

    private static final Log log = LogFactory.getLog(UserServiceImpl.class);
    private UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository){
        this.userRepository = userRepository;
    }


    public User findByUserId(String userId) {
        Optional&amp;amp;amp;lt;User&amp;amp;amp;gt; user = userRepository.findOne(userId);
        if (user.isPresent()) {
            log.debug(String.format("Read userId '{}'", userId));
            return user.get();
        }else
            throw new UserNotFoundException(userId);
    }

   
    public List&amp;amp;amp;lt;User&amp;amp;amp;gt; findAll() {
        Optional&amp;amp;amp;lt;List&amp;amp;amp;lt;User&amp;amp;amp;gt;&amp;amp;amp;gt; user = userRepository.findAll();
        return user.get();      
    }
   
    public User saveUser(User user) {
        return userRepository.saveUser(user);
    }
 
    public void updateUser(User user) {
        userRepository.updateUser(user);
    }    
    public void deleteUser(String userId) {
        userRepository.deleteUser(userId);
    }
}

En el caso de no localizar un usuario por su userId, se lanzará la excepción UserNorFoundException

package net.robertocrespo.microservices.users.exception;

import org.springframework.core.NestedRuntimeException;

public class UserNotFoundException extends NestedRuntimeException {
    public UserNotFoundException(String userId) {
        super(String.format("User with  Id '%s' not founded", userId));
    }
}
4 – Definir Controller

A continuación la implementación del UserController:

package net.robertocrespo.microservices.users.controller;

import net.robertocrespo.microservices.users.exception.UserNotFoundException;
import net.robertocrespo.microservices.users.model.User;
import net.robertocrespo.microservices.users.service.UserService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import javax.validation.Valid;

@RestController
@RequestMapping("users")
public class UsersController {

		
    private static final Log log = LogFactory.getLog(UsersController.class);

    private final UserService usersService;
    private User user;
   
    @Autowired
    public UsersController(UserService usersService) {
        this.usersService = usersService;
    }

    @RequestMapping(value="/{userId}",method = RequestMethod.GET)
    @ApiOperation(value = "Find an user", notes = "Return a user by Id" )
    public ResponseEntity&amp;amp;amp;amp;amp;lt;User&amp;amp;amp;amp;amp;gt; userById(@PathVariable String userId)  throws  UserNotFoundException{
        log.info("Get userById");
        try{
        	user = usersService.findByUserId(userId);
        }catch(UserNotFoundException e){
        	user = null;              			
        }     
        return ResponseEntity.ok(user);
        
    }
    
    @RequestMapping(method = RequestMethod.GET)
     public ResponseEntity&amp;amp;amp;amp;amp;lt;List&amp;amp;amp;amp;amp;lt;User&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;gt; userById(){
        log.info("Get allUsers");
        return ResponseEntity.ok(usersService.findAll());
    }

    @RequestMapping(method = RequestMethod.GET)
    @ApiOperation(value = "Find all user", notes = "Return all users" )
    public ResponseEntity&amp;amp;amp;amp;amp;lt;List&amp;amp;amp;amp;amp;lt;User&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;gt; userById(){
        log.info("Get allUsers");
        return ResponseEntity.ok(usersService.findAll());
    }

    @RequestMapping(value="/{userId}",method = RequestMethod.DELETE)
    @ApiOperation(value = "Delete an user", notes = "Delete a user by Id")
    public ResponseEntity&amp;amp;amp;amp;amp;lt;Void&amp;amp;amp;amp;amp;gt; deleteUser(@PathVariable String userId){
    	log.info("Delete user " + userId);
        usersService.deleteUser(userId);
        return ResponseEntity.noContent().build();
    }

    @RequestMapping(method=RequestMethod.POST)
    @ApiOperation(value = "Create an user", notes = "Create a new user")
    public  ResponseEntity&amp;amp;amp;amp;amp;lt;User&amp;amp;amp;amp;amp;gt; saveUser(@RequestBody @Valid User user){
    	log.info("Save new user");
         return ResponseEntity.ok(usersService.saveUser(user));
    }
}
5 – Crear aplicación ejecutable

Por último, solo nos queda crear la clase que ejecute la aplicación Spring Boot:

package net.robertocrespo.microservices.users;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
public class UsersApplication {

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

Por defecto, cuando inicias una aplicación spring boot , se busca un fichero llamado application.properties o application.yml para acceder a su configuración, el cual deberá estar ubicado en la carpeta resources de nuestro proyecto. Su configuración es la siguiente:

# Spring properties
spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      uri: mongodb://localhost/test
  
# HTTP Server
server:
  port: 4444   # HTTP (Tomcat) port

 

Prueba que el microservicio funciona correctamente

Probaremos el servicio por medio del interface RESTful que ofrece. Para facilitarte esta tarea, te recomiendo utilizar un cliente que te permita realizar peticiones HTTP, como Postman o Advance REST Client

Antes de nada, recuerda tener levantado el demonio de MongoDB mediante mongod.

 A continuación te indico la configuración que debes establecer en las peticiones HTTP para ejecutar las distintas operaciones CRUD del microservicio:

  • Crear nuevo usuario:
    • POST: https://localhost:4444/users
    • Header
      • Content-Type: application/json
      • Accept: application/json
    • Body:
      • {«userId»:»1″,»name»:»Rob»}
      • {«userId»:»2″,»name»:»Peter»}
      • {«userId»:»1″,»name»:»Rob»}
  • Modificar usuario:
    • PUT: https://localhost:4444/users
    • Header
      • Content-Type: application/json
      • Accept: application/json
    • Body:
      • {«userId»:»1″,»name»:»John»}

En el siguiente post aprenderemos cómo documentar y probar este API REST con Swagger

Referencias:

Comparte este artículo si te gustó:

7 Responses

  1. Hola gracias por el post, me puedes recomendar algún post o sitio que muestre como configurar el acceso a una base de datos en MongoDB Atlas, no he podido encontrar un ejemplo que use solo la variable spring.data.mongodb.uri en application.properties, todos los ejemplos usan una base de datos de MongoDB local, gracias de ante mano.

  2. Muy bueno este post, pero me gustaría que hiciesen unos post con Oracle actualizando a través de procedimientos y utilizando oAuth2 como servicio de control de las api a traves jwt.

    Donde yo trabajo no nos permites interacturar directamente con las tablas de datos. Para todos los casos se deben utilizar procedimienos y funciones, como una medida de protección. Y tengo entendido que en otras empresas sucede lo mismo.

    Desde ya agradezco estos post porque nos enseñan muchísimo.

  3. Gracias por el post, estoy comenzado con Mongodb y me ha servido de mucho tú post, sobre todo a la hora de integrarlo con Spring.

    Un saludo.

  4. Buenas tardes quisiera me colabore como implementar una consulta aleatoria mongodb y sprinboot la realizo de esta forma:

    @Query(«{ $sample: { size: 1 } }»)
    List findAllBySize();

    me salen el siguiente error
    «timestamp»: «2019-01-24T19:00:53.968+0000»,
    «status»: 500,
    «error»: «Internal Server Error»,
    «message»: «Query failed with error code 2 and error message ‘unknown top level operator: $sample’ on server localhost:27017; nested exception is com.mongodb.MongoQueryException: Query failed with error code 2 and error message ‘unknown top level operator: $sample’ on server localhost:27017»,
    «path»: «/myapplication/api/home»

  5. Buenas tardes quisiera me colabore con este error que tengo
    requiero realizar una consulta personaliza en mongo pero al momento no me funciona

  6. Hola Roberto estan geniales tus post y los estoy siguiendo, además se lo estoy recomendando a mis colegas, te quiero hacer una pregunta en una arquitectura orientada a MS, Mongo DB es utilizada para almacenar los usuarios o para que es comunemte utilzada?. Saludos y gracias de antemano

    • Hola!
      Te agradezco mucho tus comentarios. Como seguramente ya sabes, MongoDB es una base de datos NoSQL orientada a documentos. Como cualquier base de datos NoSQL, son útiles para guardar principalmente información no estructurada, es decir, estructuras de datos que pueden variar en el tiempo. En las bases de datos relaciones, asumir esos cambios es mas costoso que las BBDD NoSQL. Te recomiendo consultar en internet información relacionada sobre BBDD relaciones vs NoSQL. Para los ejemplos que estamos haciendo, podíamos haber trabajado perfectamente con una base de datos relacionar como MySQL o PostgreSQL.
      Saludos

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.