Contenido
Continuando con el microservicio construido en la parte 1 de este post, en esta segunda parte agregaremos el acceso a datos mediante consultas y actualizaciones a una base de datos relacional. En la parte final lo enriqueceremos con algunas de las características que hacen tan poderoso a Spring Framework y corregiremos fallas de diseño que ya tiene nuestro servicio.
Como recordarás, en la primera parte de este post dividimos la aplicación en las tres capas lógicas del siguiente diagrama y construimos la capa de interfaz.
En esta entrega construiremos la capa de lógica de negocio y la de de acceso a datos.
Creando el modelo de datos
Spring Boot nos ofrece múltiples formas de almacenar nuestros datos mediante un gran número de integraciones con repositorios populares como bases de datos no relacionales (MongoDB, Cassandra, etc), directorios LDAP y bases de datos relacionales tradicionales (MySQL, PostgreSQL, Oracle, etc) utilizando JDBC y la especificación JPA.
Si seguiste la primera parte de este post, ya tienes incluidas las librerías de JPA, Hibernate y el driver JDBC para la base de datos (relacional) H2 en tu archivo pom.xml, lo que resta es crear la entidad que representará a una tabla de la base de datos. Para esto utilizaremos únicamente anotaciones estándar de JPA y usaremos nuestra clase Contact para convertirla en un entidad, así:
// (1) import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; // (2) @Entity public class Contact implements Serializable { // (3) private static final long serialVersionUID = 4894729030347835498L; // (4) @Id @GeneratedValue private Long id; private String firstName; private String lastName; private String phoneNumber; private String email; ...
- Imports nuevos para agregar anotaciones de JPA
- Indicamos que esta clase representa a una tabla de base de datos con el mismo nombre. Para cambiar el nombre de la tabla podemos usar la anotación @Table
- Es buena práctica hacer las entidades serializables. Algunos proveedores de persistencia (Hibernate, EclipseLink, etc.) pueden presentar excepciones si en algunos casos particulares la entidad no es serializable.
- La anotación @Id indica que este atributo será la clave primaria y @GeneratedValue indica la forma en que se generarán los valores de la clave primaria. En este caso se usará el valor por defecto que hace que se use el valor que genere la base de datos. El comportamiento se puede cambiar por ejemplo, a una secuencia de Oracle, usando el parámetro «strategy»
Capa de acceso a datos
Spring Data JPA, que ya está entre nuestras dependencias, facilita mucho la creación de clases que guarden y recuperen datos de la base de datos usando JPA. Tradicionalmente debíamos crear manualmente la conexión a base de datos (datasource), configurar entity manager/unidad de persistencia y crear todo el código CRUD en una clase auxiliar. Este es un trabajo repetitivo y propenso a errores por lo que Spring Data JPA se encarga de estas configuraciones automáticamente, atendiendo convenciones que podemos cambiar por configuración, de llegar a requerirlo.
La forma más rápida y segura de crear una clase de acceso a datos es usar la intefaz JpaRepository de Spring Data JPA. Para esto, crea una interfaz vacía en el paquete dao que la extienda, así:
package com.sinbugs.contacts.dao; import org.springframework.data.jpa.repository.JpaRepository; import com.sinbugs.contacts.dto.Contact; public interface ContactRepository extends JpaRepository<Contact, Long> { }
Fíjate que esta interfaz extiende la interfaz JpaRepository. Durante la inicialización de la aplicación, Spring Data busca estas interfaces y crea clases que las implementan, ofreciendo automáticamente los métodos típicos para Crear, Actualizar o Eliminar (CRUD) una entidad. Los parámetros de esta interfaz son: la entidad a gestionar (Contact) y el tipo de dato de su clave primaria (Long).
Capa de lógica
En este caso esta capa es muy simple pues no requerimos hacer operaciones con reglas de negocio o cálculos complejos y por ahora, sólo vamos a guardar los datos tal como nos llegan. En un servicio más complejo, es aquí en donde debemos dejar todas las operaciones que implementen la lógica de negocio, por ejemplo, consultar servicios externos, hacer validaciones complejas, calcular otros campos, etc., haciendo que nuestro @RestController tenga la menor cantidad de lógica posible.
Crea la clase «ContactService» en el paquete «service» así:
// (1) @Service public class ContactService { // (2) @Autowired ContactRepository dao; // (3) public Contact save(Contact contact){ return dao.saveAndFlush(contact); } }
- La anotación o estereotipo «@Service» indica a Spring que cree una instancia de esta clase (bean) que podremos usar en otras instancias
- @Autowired es equivalente a @Inject que se usa en ambientes JavaEE, incluso esta última funciona con Spring también. Se utiliza para indicar a Spring que queremos que asigne una instancia de un bean a la variable que tiene la anotación @Autowired
- Nuestro método de lógica de negocio. En este caso la lógica es mínima pero es en este tipo de métodos y clases en donde debemos concentrar las operaciones, condiciones, reglas y demás acciones de lógica de negocio de nuestra aplicación
Usando la lógica desde la interfaz
Ya tenemos la lógica de negocio, como la usamos desde nuestra capa de interfaz? Debemos modificar la clase ContactsApi para que haga uso de nuestra clase de lógica de negocio, así:
... // (1) @Autowired ContactService contactService; // (2) @RequestMapping(value="/contact", method=RequestMethod.POST) public Contact updateOrSave(@RequestBody Contact contact){ // (3) return contactService.save(contact); } ...
- Indicamos a Spring que debe inyectar una instancia de nuestra clase de lógica de negocio
- Creamos un método REST que mediante una petición HTTP POST en la url http://localhost:8080/contact. Fíjate en la anotación @RequestBody antes de la variable «contact». Esta anotación indica que la variable debe ser creada con los valores que lleguen en el cuerpo de la petición HTTP, para lo cual usaremos una estructura JSON
- Invocamos lógica de negocio contenida en nuestra clase ContactService
Para hacer la petición POST podemos utilizar herramientas como SoapUI o Postman. En la siguiente imagen se muestra una petición utilizando Postman (3, en la parte superior) y la respuesta del servicio (4, en la parte inferior):
- Url de nuestro servicio web y método POST seleccionado
- Encabezado de la petición que indica el tipo de petición (Content-Type: application/json)
- Cuerpo de la petición. Es una estructura JSON que representa un contacto. Observa que no se ha indicado el valor del campo Id ya que este campo es generado por la base de datos, según las anotaciones de nuestra entidad
- Respuesta del servicio. Observa que el servicio nos retorna la entidad con un valor númerico autogenerado en el campo Id
Advertencia!
Ya tienes un servicio web REST básico funcionando, integrado con una base de datos relacional en memoria. Ha sido muy fácil y rápido construirlo y no es algo muy diferente a lo que puedes encontrar en tutoriales en internet. Lo hemos hecho de este modo para facilitar la comprensión de los conceptos básicos a los principiantes, sin embargo este servicio no está listo para ser usado en una aplicación real porque tiene fallas de diseño que dificultarán su evolución, las pruebas automatizadas y van en contra del bajo acoplamiento que debe tener un buen diseño. Corregiremos estos problemas en nuestro próximo post.
No olvides que puedes encontrar el código del tutorial completo en nuestro repositorio en GitHub
Revisa las otras partes de este post:
Anonimo
21 octubre, 2017 — 10:46 pm
Hola
Por medio de Postman me sale el siguiente error :
{
«timestamp»: 1508643867526,
«status»: 400,
«error»: «Bad Request»,
«exception»: «org.springframework.http.converter.HttpMessageNotReadableException»,
«message»: «Required request body is missing: public com.beitech.orders.dto.Product com.beitech.orders.api.OrderApi.updateOrSave(com.beitech.orders.dto.Product)»,
«path»: «/contact»
}
Omar
10 febrero, 2018 — 11:02 pm
En el Entity Contact, agreguen un constructor sin parametros para que no les salga el error en el Postman.
ejm:
public Contact() {
}
Anonimo
21 octubre, 2017 — 11:21 pm
Hola
el error cambio al siguiente
{
«timestamp»: 1508646055164,
«status»: 400,
«error»: «Bad Request»,
«exception»: «org.springframework.http.converter.HttpMessageNotReadableException»,
«message»: «JSON parse error: Can not construct instance of com.beitech.orders.dto.Product: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.beitech.orders.dto.Product: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)\n at [Source: java.io.PushbackInputStream@606ad593; line: 1, column: 2]»,
«path»: «/contact»
}
main_bug
25 octubre, 2017 — 9:05 pm
Hola. El código HTTP de respuesta (campo status) es 400. Los códigos 4xx indican que hay algo mal en la petición que se está enviando al servidor. En este caso viendo el mensaje de error parece que estas enviando una estructura JSON a un controller que recibe un objeto de tipo com.beitech.orders.dto.Product pero esa clase no tiene un constructor vacío. La especificación JavaBeans en la cual se apoyan muchos frameworks como Spring (Jackson en este caso) usan esta convención y requieren un constructor vacío para poder crear el objeto. Agrega el constructor sin parámetros a la clase com.beitech.orders.dto.Product e intenta nuevamente.
adrian
3 febrero, 2018 — 7:21 am
hola
por medio de posman me sale este error:
{«timestamp»:1517660387516,»status»:405,»error»:»Method Not Allowed»,»exception»:»org.springframework.web.HttpRequestMethodNotSupportedException»,»message»:»Request method ‘POST’ not supported»,»path»:»/product»}
Bianco
26 marzo, 2018 — 11:00 am
”path”:”/product” eso quiere decir que un un paso se cambio el nombre del servicio a contact
ISAAC
12 julio, 2018 — 12:27 pm
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Jul 12 12:00:15 CDT 2018
There was an unexpected error (type=Method Not Allowed, status=405).
Request method ‘GET’ not supported
Angelo
31 marzo, 2018 — 1:17 pm
{
«timestamp»: 1522519645859,
«status»: 500,
«error»: «Internal Server Error»,
«exception»: «java.lang.NullPointerException»,
«message»: «No message available»,
«path»: «/main»
}
me sale este error en postman
main_bug
22 julio, 2018 — 6:59 pm
Estás enviando una petición a la ruta /main para la cual no existe controlador en esta aplicación. Por ser un error 500, la información del problema la encuentras en el servidor de la aplicación. Mira esta entrada para saber como sacarle más provecho a tu log
Mario Moreno
21 junio, 2018 — 3:18 pm
Estoy obteniendo este error
{
«timestamp»: «2018-06-21T20:15:32.679+0000»,
«status»: 400,
«error»: «Bad Request»,
«message»: «JSON parse error: Cannot deserialize instance of `com.sinbugs.contacts.dto.Contact` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.sinbugs.contacts.dto.Contact` out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 1, column: 1]»,
«path»: «/contact/»
}
main_bug
22 julio, 2018 — 7:01 pm
El cuerpo de la petición que estás enviando al servidor, está mal formada. Verifica el JSON por ejemplo usando un validador de JSON en internet. Pareciera que tienes un salto de línea al principio del mensaje
German Ariza
13 julio, 2018 — 10:23 am
En la Entity faltó crear el constructor vacio.
main_bug
22 julio, 2018 — 6:02 pm
Hola y gracias por leer. Si, hizo falta, pero es intencional para mantener el código simple. En una aplicación real hará falta poner eso y lo demás que se necesite
German Ariza
13 julio, 2018 — 10:24 am
posdata: Muchas gracias por el Tutorial, muy bueno!!
Luis Pilay
7 agosto, 2018 — 4:45 pm
Podría ayudarme por favor.
Esa parte del Entity no la entendí muy bien si alguien me explica se lo agradecería millón!!
Error starting ApplicationContext. To display the auto-configuration report re-run your application with ‘debug’ enabled.
2018-08-07 16:40:35.990 ERROR 3744 — [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field contactService in com.sinbugs.contacts.api.ContactsApi required a bean of type ‘service.ContactService’ that could not be found.
Action:
Consider defining a bean of type ‘service.ContactService’ in your configuration.
————————————————————————
BUILD FAILURE
————————————————————————
Total time: 54.157s
Finished at: Tue Aug 07 16:40:36 COT 2018
Final Memory: 12M/104M
————————————————————————
Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.2.1:exec (default-cli) on project contacts-ws: Command execution failed. Process exited with an error: 1 (Exit value: 1) -> [Help 1]
To see the full stack trace of the errors, re-run Maven with the -e switch.
Re-run Maven using the -X switch to enable full debug logging.
For more information about the errors and possible solutions, please read the following articles:
[Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
main_bug
22 noviembre, 2018 — 7:52 am
Esta es la parte clave del log: «Field contactService in com.sinbugs.contacts.api.ContactsApi required a bean of type ‘service.ContactService’ that could not be found». Significa que Spring no encontró una clase anotada con @Service, @Component, @Bean u otra similar. Revisa la definición de la clase «ContactService» que seguramente te falta una de esas anotaciones
johanna
4 noviembre, 2018 — 12:05 pm
«timestamp»: «2018-11-04T17:03:39.753+0000»,
«status»: 404,
«error»: «Not Found»,
«message»: «No message available»,
«path»: «/animalesspring:%20%20data:%20%20%20%20mongodb:%20%20%20%20%20%20host:%20localhost%20%20%20%20%20%20port:%2027017%20%20%20%20%20%20uri:%20mongodb://localhost/test%20%20%20»
}
Me puedes colaborar me sale esto en postmant
main_bug
22 noviembre, 2018 — 8:02 am
Hola. La url a la que estás enviando la petición no es una url válida, lo indica el código de rror http (404) y el atributo path de la respuesta.
jhon
3 septiembre, 2020 — 12:05 am
Muchas gracias por agregar contenido tan útil. Me ha servido mucho!