Introducción a Spring Boot: Aplicación Web con servicios REST y Spring Data JPA

Última actualización: 26/11/2018

logo springEn el tutorial Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache, vimos de forma práctica los pasos a seguir para configurar una aplicación Maven que integre Spring con JPA, utilizando Hibernate como implementación. No es difícil, pero resulta tedioso tener que replicar esta configuración cada vez que empecemos un proyecto nuevo y la configuración puede complicarse si por ejemplo vamos a desarrollar un proyecto web completo y necesitamos también utilizar otros módulos de Spring tales como MVC, Data, Security, etc.

Numerosos programadores y empresas recurren a la creación de plantillas genéricas, por ejemplo en la forma de arquetipos Maven, que eviten tener que invertir tiempo en la creación y configuración de los nuevos proyectos desde cero (aunque esto conlleva el problema adicional de tener que mantener actualizadas dichas plantillas). Otra opción es utilizar plantillas de terceros disponibles en GitHub como por ejemplo las indexadas en yeoman, o irnos directamente a frameworks basados en Spring como JHipster aunque aquí ya entramos en el terreno de los generadores de código.

Teniendo en cuanta esta problemática, allá por 2014 se publicó la primera versión estable de Spring Boot, un módulo que pretende solucionar estos problemas y acelerar el desarrollo de aplicaciones basadas en Spring gracias a una gestión automatizada de la infraestructura software que necesitamos en nuestra aplicación. En concreto: “Spring Boot proporciona una manera rápida de construir aplicaciones. Inspecciona el classpath y los beans que tengas configurado, hace asunciones razonables sobre lo que te falta y lo añade. Con Spring Boot puedes centrarte más en la lógica de negocio y menos en la infraestructura”.

Hay que tener presente que Spring Boot no es un framework al uso ni un generador de código, se centra en la configuración e integración de las dependencias que solemos necesitar de forma genérica para que sin ningún esfuerzo empecemos directamente a desarrollar nuestra aplicación. Incluso configura de forma embebida un servidor Tomcat o Jetty si así lo deseamos. Y lo mejor de todo es que no supone una limitación ya que podemos seguir realizando cualquier configuración de igual forma que si no tuviéramos Spring Boot.

En este tutorial daremos los primeros pasos para empezar a utilizar Spring Boot en nuestros proyectos. En concreto, crearemos una aplicación web con una simple pantalla que muestre un listado de datos obtenidos desde una tabla de una base de datos MySQL así como una pequeña API REST de ejemplo. Asumo que el lector tiene conocimientos, a nivel muy básico, de Maven, Spring Data JPA y Spring MVC porque voy a centrarme sobre todo en Spring Boot.

En lo que respecta al testing de la aplicación (fundamental), he escrito otro tutorial específico que puede verse como la segunda parte del presente.

Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas

Se usará Spring Boot 2 aunque se indican las diferencias más relevantes con respecto a Spring Boot 1.5 de las funcionalidades que veamos.

Creando un proyecto con Spring Boot para Spring Data JPA

Vamos a crear y configurar un proyecto Maven con Spring Boot similar al de los tutoriales de Spring Data JPA pero con una capa web (JSP y REST). Como es habitual, empezamos configurando el pom.xml con las dependencias que necesitamos, y aquí viene la primera ventaja de adoptar Spring Boot: no definiremos como dependencias directamente los módulos de Spring necesarios (web, orm, security) sino una suerte de “metadependencias” (starters) de Spring Boot que incluyen todo lo necesario. Las versiones de las dependencias son debidamente seleccionadas y testeadas por el equipo de Spring Boot por lo que no tenemos que preocuparnos por posibles incompatibilidades entre librerías. Para ello añadimos lo siguiente al pom.xml:

  1. Utilizar Spring Boot como módulo padre de nuestro proyecto. Usaremos Spring Boot 1 para Spring 4 (el core de Spring es compatible con Java 6 pero dependencias como Hibernate van a requerir Java 8) y Spring Boot 2 para Spring 5 (Java 8).
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.0.6.RELEASE</version>
    		<relativePath />
    	</parent>
    
    

    Nota: si no queremos o podemos utilizar Spring Boot como proyecto padre, consultar la configuración alternativa.

  2. Añadir todos los metamódulos (starters) de Spring Boot que encapsulen las funcionalidades que necesitemos. Los starters disponibles se pueden consultar directamente en GitHub en este enlace. En nuestro caso, para utilizar Spring Data JPA con Hibernate necesitaremos el siguiente:
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
  3. Adicionalmente también necesitamos el driver JDBC que en nuestro caso es MySQL.
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  4. Opcionalmente se puede utilizar el plugin de Maven de Spring Boot que proporciona algunos “goals” interesantes para ejecutar y empaquetar nuestras aplicaciones.
    <build>
    	<plugins>
    		<plugin>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-maven-plugin</artifactId>
    		</plugin>
    	</plugins>
    </build>
    

Cursos aplicaciones móviles

Spring Boot utiliza Logback, pero acostumbro a utilizar Log4j en mis proyectos. Para hacer este cambio, es necesario incluir el starter spring-boot-starter-log4j y excluir spring-boot-starter-logging de los otros starters de los que sea dependencia

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j</artifactId>
  <version>1.3.8.RELEASE</version>
</dependency>

El pom del proyecto inicial (será modificado a medida que avancemos en el tutorial) queda así

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.danielme.demo</groupId>
	<artifactId>spring-boot</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>

	<name>spring-boot-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.6.RELEASE</version>
		<relativePath />
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j</artifactId>
			<version>1.3.8.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>


	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

Con el comando mvn dependency:list podemos comprobar todas las librerías que tenemos en nuestro proyecto.

Ahora tenemos que definir los parámetros de configuración de nuestra aplicación (las propiedades admitidas por las distintas librerías, al final de este tutorial explico cómo añadir propiedades personalizadas). Spring Boot los lee por defecto del fichero /src/main/resources/application.properties o /src/main/resources/application.yaml y en este listado encontramos los más habituales aunque no se incluyen los de todas las librerías con las que Spring Boot se integra de forma automática.

Para nuestro ejemplo necesitamos como mínimo la url de conexión a la base de datos y las credenciales. Adicionalmente establecemos también el modo DDL de Hibernate, que por defecto es none, a update para que se sincronicen nuestras entidades JPA con las tablas de la base de datos (¡No usar esto en producción si no tenemos claro lo que estamos haciendo!).

spring.datasource.url=jdbc:mysql://localhost:3306/country
spring.datasource.username=demo
spring.datasource.password=demo
spring.jpa.hibernate.ddl-auto=update

Vamos a añadir una entidad de JPA y su repositorio de JPA.

La entidad será auditable siguiendo el mecanismo visto en el tutorial Persistencia en BD con Spring Data JPA (III): Auditoría.

package com.danielme.springboot.entities;

import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@EntityListeners({ AuditingEntityListener.class })
@MappedSuperclass
public class AuditableEntity {

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Calendar createdDate;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Calendar lastModifiedDate;

    @CreatedBy
    @Column(nullable = false)
    private String createBy;

    @LastModifiedBy
    private String lastModifiedBy;

     getters y setters...
}

También vamos a necesitar la clase que establece el auditor de cada operación.

package com.danielme.springboot.repositories;

import java.util.Optional;

import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;

@Component
public class CustomAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("test");
    }

}

En la entidad tenemos una clave primaria de tipo identidad y dos atributos que no pueden ser nulos, uno de ellos además de valor único para todas las entidades.

package com.danielme.springboot.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "countries")
public class Country extends AuditableEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    @Column(nullable = false)
    private Integer population;

    public Country() {
        super();
    }

    public Country(String name, Integer population) {
        super();
        this.name = name;
        this.population = population;
    }

    getters y setters...

}

Para el repositorio no se define ninguna operación específica, para nuestro ejemplo es más que suficiente con las operaciones que Spring Data JPA proporciona de serie.

package com.danielme.springboot.repositories;

import org.springframework.data.jpa.repository.JpaRepository;

import com.danielme.springboot.entities.Country;

public interface CountryRepository extends JpaRepository<Country, Long>{

}

Vamos a seguir una arquitectura multicapa clásica basada en persistencia-servicios-vista. Añadimos un servicio para trabajar con los países que simplemente hace de intermediario con el repositorio.

package com.danielme.springboot.services;

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

import org.springframework.stereotype.Service;

import com.danielme.springboot.entities.Country;
import com.danielme.springboot.repositories.CountryRepository;

@Service
public class CountryService {

    private final CountryRepository countryRepository;

    public CountryService(CountryRepository countryRepository) {
        this.countryRepository = countryRepository;
    }

    public List<Country> findAll() {
        return countryRepository.findAll();
    }
    
    public Optional<Country> findById(Long id) {
        return countryRepository.findById(id);
    }
    
    public Long insert(Country country) {
        country.setId(null);
        return countryRepository.save(country).getId();
    }
}

Echemos un vistazo a la estructura actual del proyecto.

Antes de continuar construyendo nuestra aplicación web, veamos cómo ejecutar el proyecto utilizando una clase con un método main. Para hacer que Spring Boot configure Spring anotamos esta la clase con @SpringBootApplication que aplica las funcionalidades proporcionadas por @Configuration, @EnableAutoConfiguration, @ComponentScan y @EnableWebMvc (esta última si tenemos Spring MVC en el classpath). En nuestro ejemplo necesitamos también activar la auditoria de entidades con @EnableJpaAuditing.

En el método main lanzamos la aplicación de forma estática. Para ejecutar código en el arranque pero después de la iniciación de Spring, implementamos la interfaz CommandLineRunner con el método run.

package com.danielme.springboot;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

import com.danielme.springboot.services.CountryService;

@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef="customAuditorAware")
public class DemoApp implements CommandLineRunner {

	private static final Logger logger = Logger.getLogger(DemoApp.class);
	
	@Autowired
	CountryService countryService;
	
	public static void main(String[] args) {
		SpringApplication.run(DemoApp.class, args);
	}

	@Override
	public void run(String... arg0) throws Exception {
		logger.info(countryService.findAll().size());		
	}	

}

Ahora ejecutamos el método main como cualquier proyecto Maven y comprobamos el log.

También podemos utilizar el plugin de Maven de Spring Boot para empaquetar toda la aplicación en un jar ejecutable sin tener que realizar ninguna acción adicional utilizando el siguiente comando.

mvn package spring-boot:repackage

Este comando crea dos .jar en el directorio target.

spring-boot:repackage

El pequeño contiene sólo las clases propias del proyecto mientras que el grande incluye todas las dependencias y puede ser ejecutado mediante java -jar.

Si queremos ejecutar la aplicación “al vuelo”, es más rápido ejecutar simplemente

mvn spring-boot:run

Para más información sobre el empaquetado de aplicaciones consultar Maven: aplicaciones ejecutables.

Pool de conexiones

Spring Boot proporciona de serie una implementación de DataSource que ofrece un pool de conexiones. Esta implementación depende de la versión que utilicemos:

HikariCP es bastante más potente que el pool de Tomcat y en los benchmark suele ofrecer un mejor rendimiento, motivo por el cual el equipo de Spring lo ha adoptado. Sin embargo, HikariCP estaba soportado en Spring Boot 1 y podemos utilizarlo siguiendo estos dos pasos:

  1. Incluir la dependencia en el pom. La versión a utilizar la delegamos en Spring Boot.
    <dependency>
      <groupId>com.zaxxer</groupId>
      <artifactId>HikariCP</artifactId>
    </dependency>
    
  2. Añadir la siguiente línea al application.properties.
    spring.datasource.type: com.zaxxer.hikari.HikariDataSource
    

En cualquier caso, los parámetros de HikariCP que queramos configurar, siempre con prudencia y en función de las necesidades de la aplicación, se definen en este mismo fichero con el prefijo spring.datasource.hikari, por ejemplo:

spring.datasource.hikari.maximumPoolSize=20: 10

Aplicación web con JSP

Convirtamos nuestro ejemplo en una aplicación web con Spring MVC que muestre el listado de países que tengamos en la base de datos siguiendo los siguientes pasos.

  1. Añadimos el starter spring-boot-starter-web. Vamos a utilizar JSP para renderizar la vista de forma dinámica, pero si hiciéramos uso de alguno de los motores de plantillas compatibles con Spring Boot (Thymelef, FreeMarker, Mustache, Velocity, Groovy Templates) tendríamos que incluir el starter correspondiente, y en este caso ya no es necesario el spring-boot-starter-web.
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  2. Al utilizarse JSP, necesitamos la dependencia para el lenguaje JSTL.
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
    </dependency>
    
  3. Spring Boot utiliza un servidor Tomcat, Jetty o Undertow embebido para desplegar la aplicación. Para utilizar Tomcat añadimos las siguientes dependencias.
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>
    		
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
    </dependency>
    
  4. Las herramientas de desarrollo de Spring Boot permiten, entre otras funcionalidades, la recarga automática de la aplicación web en el Tomcat al detectarse cambios en los ficheros incluidos en el classpath.
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    
  5. El valor del packaging debe ser war.

    <packaging>war</packaging>

  6. En el application.properties definimos el prefijo y sufijo que utilizará el ViewResolver de Spring MVC para encontrar los ficheros .jsp que renderizarán las respuestas de las peticiones web.
    spring.mvc.view.prefix= /WEB-INF/jsp/
    spring.mvc.view.suffix= .jsp
    

Con estos sencillos pasos ya hemos convertido nuestra aplicación de ejemplo en una aplicación web. Vamos a desarrollar un controlador que atienda la raíz de la dirección url para obtener los países de la base de datos utilizando el repositorio de Spring Data que ya tenemos. Un script JSP será el responsable de renderizar el html final que se enviará al navegador del usuario.

Marcamos la clase con la anotación @Controller (tener siempre presente que el scope de un controller es singleton como cualquier otro bean de Spring) y en el método que procesa la petición indicamos la url que “escucha” con la anotación @RequestMapping. Opcionalmente podemos definir el tipo de petición (GET, POST, PUT…) que puede tratar el método, si no se indica se atenderán todos. Dentro del método obtenemos la lista de países y se añade al Model de Spring MVC (en la práctica esto es equivalente a poner la lista en un atributo del request). Por último, devolvemos una cadena con la ruta del script JSP la cual consiste en todo lo que va entre los valores de los parámetros spring.mvc.view.prefix y spring.mvc.view.suffix, esto es, /WEB-INF/jsp/countriesList.jsp

package com.danielme.springboot.controllers;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import com.danielme.springboot.services.CountryService;

@Controller
public class CountryController {

    private static final Logger logger = Logger.getLogger(CountryController.class);

    private final CountryService countryService;

    public CountryController(CountryService countryService) {
        this.countryService = countryService;       
    }

    @RequestMapping("/")
    public String list(Model model) {
        model.addAttribute("countriesList", countryService.findAll());
        return "countriesList";
    }
}

Ubicamos countriesList en el directorio /src/main/WEB-INF/jsp tal y como hemos indicado en el application.properties. Mostramos los datos iterando sobre el atributo countriesList utilizando la librería de etiquetas estándar JSTL.

<%@ page contentType="text/html; charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

<!DOCTYPE html>
<html>
<head>
    <link rel="shortcut icon" type="image/png" href="/favicon.png">
    <title>Spring Boot</title>
</head>
<body>
	
<h1>Countries</h1>
	
<hr>
	<c:choose>
		<c:when test="${not empty countriesList}">
                    <ul>
		               <c:forEach var="item" items="${countriesList}">
                           <li>${item.name}:<fmt:formatNumber value="${item.population}" /></li>
                       </c:forEach>
                    </ul>
		</c:when>
		<c:otherwise>
			<b>NO DATA</b>
		</c:otherwise>
	</c:choose>

</body>
</html>

Los ficheros con recursos estáticos tales como imágenes, css, JavaScript, etc, se ubican por defecto en el directorio /src/main/resources/static. Son accesibles a partir de la url raíz de la aplicación, por ejemplo /src/main/resources/static/js/script.js estará en http://localhost:8080/js/script.js.

Para arrancar la aplicación web tenemos varias opciones:

  • Crear un .war ejecutable con el comando que vimos antes (mvn package spring-boot:repackage).
  • Lanzar directamente la aplicación con mvn spring-boot:run.
  • Lanzar la aplicación, tanto en modo “normal” como en debug, en Eclipse utilizando el plugin de Spring instalable desde el Marketplace. También podemos descargar desde la web de Spring un Eclipse ya empaquetado con este plugin. En cualquier caso, en el menu contextual tanto del proyecto como de la clase con el Main tendremos la opción Spring Boot App dentro de Run As y Debug As.

    spring tool suite spring boot app

    Una vez lanzado el servidor, se puede detener desde la propia consola de Eclipse.

La aplicación está disponible en la url http://localhost:8080. El puerto se puede cambiar con la propiedad server.port en el application.properties.

Normalmente necesitaremos empaquetar las aplicaciones web en un war estándar que pueda ser desplegado directamente en un contenedor o servidor de aplicaciones como Tomcat. Conseguir esto es tan sencillo como hacer que la clase Main herede de SpringBootServletInitializer, clase que depende de la versión de Spring Boot:

@SpringBootApplication
public class DemoApp extends SpringBootServletInitializer
{

NOTA: Si lanzamos el proyecto desde Eclipse, la url es http://localhost:8080/spring-boot/.

Con este pequeño cambio el war generado con Spring Boot sigue siendo autoejecutable con el Tomcat embebido pero también puede desplegarse en cualquier Tomcat o similar (renombrar y utilizar el fichero spring-boot-1.0.war.original). Si queremos prescindir de la funcionalidad de autoejecución, hay que eliminar del pom las dependencias spring-boot-starter-tomcat y tomcat-embed-jasper. Esto provoca como efecto colateral que desde Eclipse la aplicación deba ser lanzada como cualquier proyecto web Maven estándar en un servidor y no con las herramientas del plugin de Spring.

Servicios REST

Vamos a añadir un pequeño servicio REST para Country. Este servicio se implementa sin utilizar ninguna funcionalidad específica de Spring Boot y voy a utilizar algunas de las anotaciones introducidas en Spring 4 tales como @RestController o @GetMapping (Spring 4.3) que simplifican la definición de los servicios.

En primer lugar, creamos un controlador de tipo RestController con una url raíz para todas las llamadas que contendrá

@RestController
@RequestMapping(CountryRestController.COUNTRY_RESOURCE)
public class CountryRestController {

    private static final Logger logger = Logger.getLogger(CountryRestController.class);
    
    public static final String COUNTRY_RESOURCE = "/api/country";

Tanto las respuestas y peticiones de nuestro servicio REST se basarán en JSON. Esta funcionalidad ya la tenemos de serie en Spring y no es necesario configurar absolutamente nada; la conversión entre objetos Java y JSON y viceversa (serialización y deserialización) es realizada de forma automática por Spring porque spring-boot-starter-web incluye la librería Jackson.

Para las respuestas voy a devolver la clase ResponseEntity que representa la respuesta HTTP completa que se devuelve al cliente del servicio. Esto me permite personalizar el código de estatus HTTP que quiero devolver, algo muy importante a la hora de hacer un buen diseño de una API.

Tenemos dos servicios REST para el recurso Country.

  • Obtención de un país a partir de su id. Es una llamada HTTP de tipo GET con el id incluido en la propia url. Para recibir en el método este valor utilizamos la anotación @PathVariable. Si el país existe se devuelve junto con un status code 200, si no simplemente se retorna un 404.
        @GetMapping(value = "/{id}/")
        public ResponseEntity<Country> getById(@PathVariable("id") Long id) {
            Optional<Country> country = countryService.findById(id);
            if (country.isPresent()) {
                return new ResponseEntity<>(country.get(), HttpStatus.OK);
            }
            return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
        }
    
  • Creación de un nuevo país. Es una llamada tipo POST directamente a la url raíz del servicio en la que el cliente debe enviar un JSON con el nombre y población del país
    {
        "name": "Germany",
        "population": 79778000
    }
    

    En el método del controller podemos recibir directamente un objeto de tipo Country añadiéndolo como un parámetro y con la anotación @RequestBody. Si la inserción es correcta, se devuelve el identificador que ha sido asignado al nuevo país, en un JSON con el atributo id, y un código 201 (creado). Si se produce una excepción, se devolverá el código 500 (Internal server error) y un JSON con el mensaje de la excepción.

        @PostMapping
        public ResponseEntity<Map<String, Object>> addWithoutValidations(@RequestBody Country country) {
            try {
                Long id = countryService.insert(country);
                return new ResponseEntity<>(Collections.singletonMap("id", id), HttpStatus.CREATED);
            } catch (Exception ex) {
                logger.error(ex.getMessage(), ex);
                return new ResponseEntity<>(Collections.singletonMap("error", ex.getMessage()),
                        HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    

    Nota: he intentado mantener el servicio lo más simple posible pero en un servicio real antes de realizar la inserción se deben validar los datos del país, por ejemplo utilizado Hibernate Validator, y devolver los posibles errores utilizando un código 400.

  • La clase completa queda así

    package com.danielme.springboot.controllers;
    
    import java.util.Collections;
    import java.util.Map;
    import java.util.Optional;
    
    import org.apache.log4j.Logger;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    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.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.danielme.springboot.entities.Country;
    import com.danielme.springboot.services.CountryService;
    
    @RestController
    @RequestMapping(CountryRestController.COUNTRY_RESOURCE)
    public class CountryRestController {
    
        private static final Logger logger = Logger.getLogger(CountryRestController.class);
        
        public static final String COUNTRY_RESOURCE = "/api/country";
    
        private final CountryService countryService;
    
        public CountryRestController(CountryService countryService) {
            this.countryService = countryService;
        }
    
        @GetMapping(value = "/{id}/")
        public ResponseEntity<Country> getById(@PathVariable("id") Long id) {
            Optional<Country> country = countryService.findById(id);
            if (country.isPresent()) {
                return new ResponseEntity<>(country.get(), HttpStatus.OK);
            }
            return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
        }
    
        @PostMapping
        public ResponseEntity<Map<String, Object>> addWithoutValidations(@RequestBody Country country) {
            try {
                Long id = countryService.insert(country);
                return new ResponseEntity<>(Collections.singletonMap("id", id), HttpStatus.CREATED);
            } catch (Exception ex) {
                logger.error(ex.getMessage(), ex);
                return new ResponseEntity<>(Collections.singletonMap("error", ex.getMessage()),
                        HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }
    
    

    La API REST se puede probar utilizando herramientas gratuitas y multiplataforma tales como Insomnia REST Client o Postman.

    En el tutorial Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas testearemos esta API REST desde código en tests de JUnit utilizando MockMvc y RestAssured

    Spring initializr

    La creación inicial del proyecto se puede realizar de forma gráfica mediante esta herramienta vía web, o bien mediante un asistente en Eclipse gracias al plugin Spring Tool Suite del que ya hemos hablado. Para utilizarlo nos vamos a “File>New->Other” y seleccionamos el asistente Spring Boot->Spring Starter Project.

    El asistente consta de tres pantallas. En la segunda de ellas tenemos acceso a un catálogo categorizado con todos los starters.

    Los proyectos creados con esta herramienta incluyen un “wrapper” para Maven o Gradle, según la opción elegida, por lo que no es necesario tener instalado como prerrequisito Maven (o Gradle) para poder construir el proyecto. Además de esta forma nos aseguramos que el proyecto se gestione siempre con la versión de Maven o Gradle correcta. En mi opinión es una buena práctica utilizarlo aunque en el proyecto de ejemplo no lo he incluido por simplicidad.

    Disponemos de dos scripts, uno para Windows (cmd) y otro para Linux (bash), que invocaremos con los mismos parámetros que usaríamos si llamáramos directamente al ejecutable de Maven (mvn).

    Propiedades personalizadas

    Hemos visto que los parámetros de configuración de Spring Boot y de las librerías que integra se definen en el fichero application.properties. Ahora vamos a ir más allá y ver cómo añadir nuestros propios parámetros a ese fichero.

    1. Creamos un POJO (o todos los que queramos) que modele las propiedades de configuración. Esta clase la anotamos con ConfigurationProperties para definir el prefijo que tendrán estas propiedades.

      package com.danielme.springboot;
      
      import org.springframework.boot.context.properties.ConfigurationProperties;
      
      @ConfigurationProperties("custom")
      public class CustomProperties {
      
          private String value;
      
          public String getValue() {
              return value;
          }
      
          public void setValue(String value) {
              this.value = value;
          }
          
      }
      
    2. Incluir esta clase en la configuración de Spring Boot con la anotación EnableConfigurationProperties.

      @SpringBootApplication
      @EnableConfigurationProperties(CustomProperties.class)
      public class DemoApp extends SpringBootServletInitializer
      {
      
    3. Le damos valor a la propiedad (custom.value).
      spring.datasource.url=jdbc:mysql://localhost:3306/country
      spring.datasource.username=demo
      spring.datasource.password=demo
      spring.jpa.hibernate.ddl-auto=update
      spring.datasource.type= com.zaxxer.hikari.HikariDataSource
      
      spring.mvc.view.prefix= /WEB-INF/jsp/
      spring.mvc.view.suffix= .jsp
      
      custom.value = Hello World!!!
    4. Para leer la propiedad, inyectamos la clase que la modela. Como ejemplo, voy a imprimir la propiedad en el log en CountryController
      package com.danielme.springboot.controllers;
      
      import org.apache.log4j.Logger;
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      import com.danielme.springboot.CustomProperties;
      import com.danielme.springboot.services.CountryService;
      
      @Controller
      public class CountryController {
      
          private static final Logger logger = Logger.getLogger(CountryController.class);
      
          private final CountryService countryService;
          private final CustomProperties customProperties;
      
          public CountryController(CountryService countryService,
                  CustomProperties customProperties) {
              this.countryService = countryService;
              this.customProperties = customProperties;
          }
      
          @RequestMapping("/")
          public String list(Model model) {
              logger.info(customProperties.getValue());
              model.addAttribute("countriesList", countryService.findAll());
              return "countriesList";
          }
      }
      
      

    Código de ejemplo

    El proyecto completo para el presente tutorial y también Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

    También se encuentran en GitHub los ejemplos oficiales de Spring Boot.

    Otros tutoriales relacionados con Spring

    Testing en Spring Boot con JUnit 4\5. Mockito, MockMvc, REST Assured, bases de datos embebidas

    Spring JDBC Template: simplificando el uso de SQL

    Persistencia en BD con Spring Data JPA

    Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache

    Testing Spring con JUnit 4

    Ficheros .properties en Spring IoC

    Master Pyhton, Java, Scala or Ruby

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.