31 de marzo de 2020

ORM por XML: Guardar subclases en SINGLE_TABLE

Ya vimos en una sesión anterior la capacidad de heredar la definición de persistencia desde las superclases con la etiqueta "mapped-superclass". En esta entrada vamos a ver cómo se guardan especializaciones distintas de un mismo tipo pudiendo recuperar todas ellas de forma única o recuperándolas como un tipo específico.

En nuestro ejemplo nuestras especializaciones son los distintos tipos de sucesos que hay en un partido (goles, tarjetas, corners, etc...) ya que todos heredan de Suceso, en nuestra API concretamente de la implementación SucesoConId.

Para persistir varias clases que heredan de un mismo tipo se puede hacer de tres formas:
  1. SINGLE_TABLE: Es la opción por defecto y la implementada por todas las librerías. Los datos de todos los subtipos se guardan en una misma tabla. Tiene el mejor rendimiento a la hora de consultar datos porque no hay que realizar ninguna unión, pero como desventaja tendremos campos null en las columnas que no usen especificaciones concretas (tienen que existir columnas para todos los campos de todas las implementaciones)
  2. JOINED: Todos los datos comunes se volcarán en una única tabla y el resto de campos se guardará en otra tabla aparte con una FK a la tabla común que lo relacione.
  3. TABLE_PER_CLASS: Esta es una opción de JPA y puede que algunas implementaciones no la contempen. Se trata de guardar cada clase entera en su propia tabla.
De estas tres formas nos vamos a centrar en SINGLE_TABLE por ser la más implementada y de mejor rendimiento aunque implique desnormalizar nuestro modelo de datos en la BD.

¿Cómo se identifica qué tipo hay en cada fila?

Como vamos a mezclar tipos distintos en la misma tabla debe haber algún mecanismo que permita saber qué tipo concreto hay en cada fila. Para esto, además de todas las columnas para los datos de todas las distintas especializaciones se va a crear una columna que sirva para discriminar el tipo. Para implementar todo lo necesario lo voy a dividir en 4 pasos:
  1. Creación de las dos especializaciones y sus DAO
  2. ORM por XML para SucesoConId y especialización de Tarjeta
  3. ORM por anotaciones de especialización de Gol
  4. Modificar nuestro ObjectMapper como sea necesario
Paso 1: Vamos a crearnos dos de estas especificaciones: GolConId y TarjetaConId que heredarán de SucesoConId y además implementarán sus respectivas interfaces (Gol y Tarjeta). También vamos a crear los DAO correspondientes como lo hicimos en la sesión anterior sobre Spring Data Rest. Éste código no tiene ningún misterio y se puede ver fácilmente en el commit.

Como ya dijimos que para JPA las interfaces no existen, no caigamos en la tentación de asignar las definiciones a las interfaces Gol y Tarjeta (ver fichero Sucesos.orm.xml): deben ser clases que se puedan instanciar. De hecho podríamos tener implementaciones distintas a GolConId y TarjetaConId (y que tuvieran que guardarse de forma distinta también) lo que implicaría otro valor para discriminarlas. Como hay dos especificaciones voy a hacer ORM de formas distintas con cada una:

Paso 2: Tarjeta por XML (lo añado a SucesoConId.orm.xml para que se vea que puede haber varias entidades en el mismo fichero):
<entity class="es.lanyu.eventos.repositorios.SucesoConId" access="FIELD">
    <table name="SUCESOS"/>
    <!-- No hace falta strategy es el valor por defecto -->
    <inheritance strategy="SINGLE_TABLE"/>
    <discriminator-column name="TIPO"/>
    <discriminator-value>S</discriminator-value>
    <attributes>
        ...
    </attributes>
</entity>

<entity class="es.lanyu.eventos.repositorios.TarjetaConId" access="FIELD">
    <discriminator-value>T</discriminator-value>
    <attributes>
        <basic name="tipoTarjeta"/>
    </attributes>
</entity>
Paso 3: Y Gol lo haré por anotaciones añadiéndole lo que hace falta a la clase GolConId:
@Entity
@Access(value=AccessType.FIELD)
@DiscriminatorValue("G")
public class GolConId extends SucesoConId implements Gol { ... }
NOTA: ver @Access.

Paso 4: Ahora toca tunear nuestro MixIns para añadir y reutilizar código (implementación y extensión de ContadorDeMinutos y refactorizado de Datables-Partidos-Sucesos). Como no es parte de esta sesión mejor verlo en el commit o el video del webinar.

Con esto ya podemos levantar el servicio y empezar a añadir también goles y tarjetas. Veremos que crea URLs para los recursos /goles y /tarjetas donde podemos ver cada uno de ellos por separado o todos juntos en el recurso de /sucesos pero con relaciones agrupándo cada tipo distinto.

Se puede obtener el código hasta aquí en su repositorio. Se han añadido peticiones a la colección de Postman para generar goles y tarjetas aleatorias.

29 de marzo de 2020

La potencia de Spring Data Rest: @RestResource

Ya hemos estado viendo lo básico del Core de Spring, también hemos estado viendo lo básico de JPA/ORM para dar persistencia a los datos. Ahora vamos a ver la capa de presentación mostrando nuestros datos por una API REST.

REST se clasifica en varios niveles en función de su madurez. En general basta con un servicio web que atienda peticiones por protocolo HTTP y devuelva datos serializados (normalmente en JSON, pero puede ser otro formato también): Nivel 0. Estos dos puntos parecen antagónicos con SOAP que permite cualquier protocolo para el transporte, pero los datos deben intercambiarse por XML con un esquema rígido.

URLs bien definidas identificando recursos: colecciones, elementos individuales y otras "operaciones": Nivel 1.

Para las peticiones se usarán verbos HTTP para operaciones CRUD (GET, POST, PUT, DELETE,...): Nivel 2.

El nivel más alto se logra consiguiendo HATEOAS/Hipermedia. Esto significa que, desde la raíz de nuestro servicio REST, todas las llamadas que se puedan realizar deben ser autodescubribles por medio de enlaces URL que representan las asociaciones de los distintos recursos: Nivel 3. Un ejemplo sería la API con datos sobre Star Wars donde se ven los enlaces de Luke con su planeta natal, peliculas donde sale, sus vehículos, raza, etc... siendo todos ellos urls al recurso (pero no incrustando sus datos).

Otra característica es que REST no mantiene estado. Cada petición y respuesta debe contener toda la información necesaria.

Después de esta introducción, recomiendo ver un video que habla de todo ello de una forma muy práctica y directa llamado "Horneando APIs" de Paradigma Digital. y que para mí es como un resumen en español del libro REST API Design Rulebook. Nosotros vamos a poner en práctica todo esto con:
  1. Spring Data Rest: que es una de las dependencias que usamos cuando generamos el proyecto inicialmente con Spring Initialzr. Si miramos nuestro build.gradle se identifica perfectamente la dependencia
  2. y nuestro ejemplo de Datos Deportivos
Empezamos este tutorial dando persistencia a Participantes y a Partidos que tenía una colección de Sucesos. Cargamos los datos de 237 participantes usando su DAO correpondiente y también cargamos unos partidos aleatorios con sucesos (en general sin decir si son goles, tarjetas, etc...). Esta forma de trabajar no permite la interacción que se espera de una arquitectura cliente-servidor. Al final de esta entrada tendremos una colección para Postman que nos permitirá interaccionar con nuestra aplicación mediante llamadas HTTP y nos permitirá hacer pruebas más fácil. Por tanto el punto inicial es este commit de nuestro repositorio.

Si en este punto no cerramos la aplicación con nuestra última linea del main veríamos que ya se están exponiendo los repositorios. Hacemos una llamada a http://localhost:8080/ y tendremos la siguiente respuesta:
{
  "_links": {
    "partidoConIds": {
      "href": "http://localhost:8080/partidoConIds{?page,size,sort}",
      "templated": true
    },
    "participantes": {
      "href": "http://localhost:8080/participantes{?page,size,sort}",
      "templated": true
    },
    "sucesoConIds": {
      "href": "http://localhost:8080/sucesoConIds{?page,size,sort}",
      "templated": true
    },
    "profile": {
      "href": "http://localhost:8080/profile"
    }
  }
}
Es decir, por defecto, todos los repositorios serán mostrados por Spring Data Rest si no se configura lo contrario. Voy añadir dos propiedades para configurar dos aspectos: que sólo se muestre lo anotado con @(Repository)RestResource y el prefijo de la API:
spring.data.rest.detection-strategy=annotated
spring.data.rest.basePath=/api
Esto lo pondré en un archivo llamada rest.properties que añadiré con @PropertySource como vimos anteriormente mediante una clase de configuración llamada ConfiguracionJava que luego usaré para más cosas:
@Configuration
@PropertySource({ "classpath:config/rest.properties" })
public class ConfiguracionPorJava {}
Y la añadimos a nuestra aplicación:
@Import(ConfiguracionPorJava.class)
public class DatosdeportivosapiApplication {...}
Ahora ya no se mostrará lo anterior, así que lo siguiente será modificar nuestras anotaciones @Repository por @RepositoryRestResource que es de Spring Data Rest.

@RepositoryRestResource admite varios valores para configurar nuestro repositorio. Pongo el ejemplo para SucesoConId:
@RepositoryRestResource(path="sucesos",
//                        exported=false,
                        itemResourceRel="suceso",
                        collectionResourceRel="sucesos")
public interface SucesoDAO extends JpaRepository<SucesoConId, Long> {
}
Aquí se puede ver que podemos decidir el path para este recurso y los nombres que tendrán las relaciones tanto para colección como para elemento (al final de la entrada se ve qué es esto de las relaciones). Recomiendo usar al menos esta configuración porque de lo contrario generará esos nombres automáticamente y puede que el resultado no nos guste (ver cómo se veía antes más arriba).

He comentado el valor exported=false ya que evita que se exponga el recurso. Este valor habría que usarlo si no quisiéramos mostrar este recurso. Por defecto tiene valor true.

Con esto termina el Paso 1, pero los recursos de Partidos y Sucesos no se pueden visitar ya que Jackson no es capaz de serializarlos correctamente porque no son simples POJOs. Vamos a customizar cómo debe serializarse aplicando propiedades para jackson, implementando unos MixIn y añadiéndolos a nuestro ObjectMapper. Como la entrada no va de esto y para no alargarla, lo mejor es ver este segundo paso en vivo en el webinar, pero aquí nos vamos a colocar en el commit del Paso 2 para continuar y terminar con nuestra API RESTful HATEOAS.

El resultado hasta aquí muestra una API con un array de Sucesos en cada Partido y sin mostrar el partido al que corresponde cada Suceso para no entrar en bucle. También vemos links de paginación dando cierta navegabilidad, pero no se puede navegar entre Partidos y Sucesos correctamente. Para implementar HATEOAS correctamente Spring necesita la siguiente información sobre las relaciones:
  1. Anotar con @OneToMany y @ManyToOne los miembros necesarios. No sirve sólo con que tenga la información JPA.
  2. Los recursos para asociar deben tener repositorio propio (esto ya está hecho)
  3. Se debe conocer la implementación concreta si la relación hace referencia a un tipo abstracto (debe saber qué Suceso debe deserializarse: deserialización polimórfica)
Para el primer punto no hace falta anotar las clases con @Entity ni @Id. Con anotar el miembro correspondiente es suficiente. Como no se tiene acceso al campo sucesos lo que hacemos es sobre escribirlo y anotarlo incluyendo la entidad objetivo (punto 3):
@Override
@OneToMany(targetEntity=SucesoConId.class)
public Collection<Suceso> getSucesos() {
    return super.getSucesos();
}
NOTA: también podría hacerse con (de)serializadores personalizados, añadiendo información con TypeResolver (y TypeResolverBuilder, sobre @JsonType(Id)Resolver o ver este snippet). Es la solución más genérica que se encuentra (aunque rebuscando mucho, una búsqueda me dio sólo 3 resultados) en Internet, pero en nuestro caso particular usando semántica esta forma es muy simple y funciona.

Para el caso de SucesoConId basta con anotar el campo con @ManyToOne.
@ManyToOne
PartidoConId partido;
Comparto también una colección, para probar la API en el punto actual.

26 de marzo de 2020

ORM por XML con relaciones @OneToMany

Los dos casos vistos hasta ahora son entidades que sólo tienen campos básicos de tipo String, pero nuestros objetos de negocio muchas veces tienen relaciones con otros objetos con un campo de su tipo o incluso contienen una lista de ellos.

En esta entrada nos vamos a centrar en objetos que tengan una lista de entidades lo que forma una relación "Uno a Muchos" que en JPA se conoce con la anotación @OneToMany o la etiqueta one-to-many (y viceversa @ManyToOne).

La relación con @OneToMany puede hacerse con una Join Table o con una clave ajena en la tabla de entidades del tipo contenido en la lista (Join Column). En esta entrada se va a ver esto último.

Para verlo voy a usar la clase Partido de datos-deportivos, ya que tiene una Collection<Suceso> que hereda de EventoImpl.

Lo primero que tenemos que hacer es implementar un código necesario previo para centrarnos después en el objeto de esta entrada. El código está en el commit previo a empezar con la definición de estas relaciones y contiene:
  1. Creación de sendas clases entidad para Partido (PartidoConId) y Suceso (SucesoConId) ya que estos tipos no tienen ningún campo que pueda usarse como Id y todas las entidades lo necesitan.
  2. En este caso los Ids serán autogenerados por la base de datos con @GeneratedValue e IDENTITY.
  3. Como todos los tipos de los que heredan Partido y Suceso son de una librería de terceros tengo que usar la configuración XML porque no puedo anotar los campos.
  4. Una vez creados los ficheros ORM hay que añadirlos a la configuración.
Estos puntos es lo que hemos visto en las entradas anteriores y veremos que se parece bastante. Viendo el vídeo hay una explicación de este código.


Usando nuestros conocimientos de modelado de datos vamos a representar la relación PartidoConId-SucesoConId añadiendo a la tabla de Sucesos la clave ajena (FK) de PartidoConId al que pertenece ese Suceso.

Para ello definimos con la etiqueta one-to-many el nombre de nuestro campo con la colección de Sucesos, el tipo de entidad destino (SucesoConId) y con qué campo se mapea (con partido - veremos qué significa esto más abajo). Usamos la siguiente etiqueta dentro de los atributos de EventoImpl:
<one-to-many name="sucesos" target-entity="es.lanyu.eventos.repositorios.SucesoConId" mapped-by="partido"/>
NOTA: Es importante destacar que el tipo objetivo debe ser una entidad, no vale con la interface Suceso, debe ser la entidad SucesoConId.

Al hacerlo de esta forma (usando una FK en una Join Column) la relación debe ser bidireccional: se puede navegar de Partido a Suceso y viceversa. Esto implica que nuestra clase SucesoConId tiene que añadir un campo partido para saber cómo se mapea esa FK. Este campo debe tener el nombre que hemos puesto en mapped-by="partido" y ser del tipo de la entidad que contiene la colección (PartidoConId).
PartidoConId partido;

public PartidoConId getPartido() {
 return partido;
}

public void setPartido(PartidoConId partido) {
 this.partido = partido;
}
Además se deben añadir los respectivos accesores (getter/setter) para este campo. Esto se debe a que hay que mantener sincronizadas las dos entidades cuando se modifique (añadir en PartidoConId otro Suceso):
public void addSucesoConId(SucesoConId suceso) {
    super.addSuceso(suceso);
    suceso.setPartido(this);
}
Por último, debemos añadir en nuestro ORM para SucesoConId la etiqueta many-to-one para cerrar la relación en los dos sentidos e indicar la columna que se usa para hacer el join:
<many-to-one name="partido" optional="false"><!-- fetch="LAZY">-->
    <join-column name="ID_PARTIDO" referencedColumnName="ID"/>
</many-to-one>
NOTA: Dejo comentado el atributo fetch="LAZY" para ver cómo se pondría si se quisiera que la relación no se traiga los datos hasta que no se necesiten.

En el código hasta aquí se puede encontrar comentadas las anotaciones que corresponden a esta configuración XML en SucesoConId. Sin embargo, podemos ver que seríamos incapaces de añadírselo a EventoImpl al no ser código que gestionemos nosotros.

Todo esto puede verse funcionando en el video anterior con un main modificado que irá insertando partidos con un suceso y lo imprime para ver el resultado.

23 de marzo de 2020

ORM por XML de clases con herencia

En la entrada anterior vimos ORM por XML de una clase simple, sin relaciones y con campos de tipos básicos (String). En esta entrada vamos a ver cómo hacer ORM por XML de una clase con herencia usando la etiqueta mapped-superclass.

Para ello vamos a usar la clase Participante del proyecto datos-deportivos que es bastante simple aunque no es un POJO pues hereda de AbstractNombrable (y pertenece a otra librería). En definitiva se trata de usar la API de JPA para definir la relación entre los campos de nuestro objeto de negocio (incluidos los declarados en la superclase) y columnas de nuestras tablas de la BD.

Para ello hay que estudiar cómo se define la clase participante. No dispone de ningún campo propio: sólo tiene identificador y nombre pero pertenecen a las clases IdentificableString y AbstractNombrable respectivamente.

Vamos a añadir a nuestra carpeta resources/jpa los archivos IdentificableString.orm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
                                     http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
                 version="1.0">

  <mapped-superclass class="es.lanyu.commons.identificable.IdentificableString"
                     access="FIELD">
    <attributes>
      <id name="id" />
    </attributes>
  </mapped-superclass>

</entity-mappings>
Y AbstractNombrable.orm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
                                     http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
                 version="1.0">

  <mapped-superclass class="es.lanyu.commons.identificable.AbstractNombrable"
                     access="FIELD">
    <attributes>
      <basic name="nombre" optional="false" />
    </attributes>
  </mapped-superclass>

</entity-mappings>
De esta forma ya tenemos definido cómo se persisten las clases de las que hereda. En esta ocasión no se usa la etiqueta entity como se va a seguir usando en Participante sino que usamos mapped-superclass para las superclases que no son entidades. Con esta etiqueta vamos a definir el mapeo de todo lo que es común al resto de entidades que hereden. Si nos fijamos cada superclase tiene definidos sus propios campos.

Sólo nos quedaría añadir el archivo con al mapeo de Participante, Participante.orm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
                                     http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
                 version="1.0">

  <entity class="es.lanyu.participante.Participante" access="FIELD">
    <table name="PARTICIPANTES"/>
    <!-- <attributes>
      <id name="id">
        <generated-value strategy="IDENTITY"/>
      </id>
      <basic name="nombre" optional="false">
        <column length="32"/>
      </basic>
    </attributes>-->
  </entity>

</entity-mappings>
Una vez definido el mapeo nos toca añadir el DAO correspondiente con las operaciones para Participante, con lo que nos creamos el sencillo PanticipanteDAO en el paquete es.lanyu.participante.repositorios. ¿Sabes crear el código por tí mismo?


package es.lanyu.participante.repositorios;

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

import es.lanyu.participante.Participante;

@Repository
public interface ParticipanteDAO extends JpaRepository {}

Y no hay que olvidarse de añadir el escaneo de este repositorio a nuestra configuración (añadimos otro elemento jpa:repositories). ¿Sabes dónde hay que añadirlo?



En el fichero resources/config/jpa-config.xml

Con esto ya estamos listos para realizar operaciones CRUD con participantes en nuestra BD.

Puedes encontrar el código hasta aquí en su repositorio y ver el video del webinar:

Te propongo como ejercicio que cargues todos los participantes que están en el fichero participantes.json en la BD programando lo que haga falta para leerlos (leyendo línea a línea cogiendo las que tienen datos y descartando las líneas de comentarios), persearlo con jackson (desde una línea con datos parsearla a Participante usando el bean ObjectMapper, ojo que hay un campo que habrá que ignorar - hashcode - usando un MixIn) y luego guardarlo con ParticipanteDAO.save. Finalmente imprime por el log con nivel TRACE los participantes que recuperes que tengan en su nombre la palabra "Real" (definir método con la query en DAO). Deben verse por consola (modificando el nivel de log correspondiente en propiedades).

Inténtalo a ver hasta donde llegas y, si no lo acabas o quieres compararla con otra solución, puedes ver el siguiente vídeo:

22 de marzo de 2020

ORM por XML de POJO simple

La entrada anterior vimos una persistencia muy básica sobre una clase propia que anotábamos. En nuestras aplicaciones usamos librerías de terceros y puede que debamos definir como se realizará la persistencia con una configuración externa al código de esa librería con lo que no se pueden usar anotaciones. En ese caso se puede:
  1. Usar Data Transfer Objects (DTO): POJOs que se utilicen para enviar información desde la capa de persistencia a la de servicio haciendo mapeos en ambos sentidos o
  2. Definir el mapeo de persistencia (ORM) del mismo tipo que se usa en el servicio usando configuración por XML.
En esta entrada empezamos a ver ORM por XML empezando por lo más sencillo: un POJO simple con campos de tipos básicos. Voy a usar la clase Usuario de la entrada anterior.

Lo primero es comentar las anotaciones que usamos para marcar la entidad (@Entity) y su clave primaria (@Id). En este momento si ejecutamos nos dirá que no existe una entidad gestionada para el tipo Usuario:

java.lang.IllegalArgumentException: Not a managed type: class es.lanyu.usuarios.repositorios.Usuario

Eso ocurre porque al intentar crear el bean para UsuarioDAO no es capaz de encontrar la definición de entidad para el tipo variable que le hemos pasado (Usuario):

Error creating bean with name 'usuarioDAO': Invocation of init method failed

Vamos entonces a definir por XML lo que hemos comentado. Lo haremos en un fichero con el nombre Usuario.orm.xml que pondremos en nuestros resources dentro de una carpeta nueva que llamaremos jpa:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
                                     http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
                 version="1.0">

  <entity class="es.lanyu.usuarios.repositorios.Usuario" access="FIELD">
    <table name="USUARIOS"/>
    <attributes>
      <id name="nombre">
        <!-- <generated-value strategy="IDENTITY"/> -->
        <column length="16"/>
      </id>
      <basic name="correo" optional="false" />
    </attributes>
  </entity>
</entity-mappings>
Este fichero utiliza una definición que no habíamos usado hasta ahora y tiene que ver con JPA, no con Spring Framework. Ahí está definida la etiqueta entity.

Para nuestro caso identificamos la etiqueta entity y su atributo class con el valor de la clase Usuario con la anotación @Entity comentado.

Por otro lado identificamos la etiqueta id, y su atributo name con valor nombre, dentro de la etiqueta attributes con la anotación @Id comentada.

Esta información es necesaria igual que vimos en el caso de las anotaciones ya que para guardar un objeto debe ser de una clase de entidad y poseer un campo que sirva de clave primaria.

El resto de campos no son necesarios añadirlos ya que por defecto serán guardados en la BD si no están marcados como transient igual que pasaba con la serialización en Java nativo. Para esto y para añadir otra información de persistencia a un campo se utiliza la etiqueta basic. En nuestro caso lo añadimos para obligar que el campo tenga un valor con optional="false".
NOTA: Haz la prueba y mira en H2 cómo están definidos los cambios. Luego borrar la tabla (DROP TABLE USUARIOS) y ejecútalo comentando el campo correo y mira la diferencia en la tabla (NOT NULL vs NULL).

Fíjate que he puesto un elemento table indicando el nombre de la tabla (USUARIOS), de esta forma nos guardará los usuarios en esa nueva tabla y podremos compararla con la tabla USUARIO. Si no ponemos nada le pondrá el nombre de la entidad como pasó con las anotaciones y de ahí salió nuestra tabla USUARIO. La traducción a anotación de este elemento table es @Table.

Ahora tenemos que decirle a nuestra aplicación que lo incluya, para eso lo añadimos a nuestra configuración en XML. ¿Sabréis decir dónde está?



En el fichero resources/config/jpa-config.xml

Si revisamos por encima el código enseguida vemos dónde se indican estos archivos de mapeo. Simplemente añadiendo el nuevo ya estará todo listo para que Spring coja la nueva configuración y pueda empezar a guardar usuarios de nuevo.

Puedes encontrar el código hasta aquí en su repositorio y ver el vídeo del webinar:

En la siguiente entrada veremos cómo hacer esto con clases que tienen superclases.

Logs Avanzado

Las propiedades del logging no se pueden separar en otro fichero .properties para añadirlo a nuestro contexto ya que el logging se crea antes que el ApplicationContext.

Siguiendo con el formato de la entrada de logging anterior, éste es el fichero logback-spring.xml que puede traducirse a lo que se vio en ella: formato de fecha, de patrón, color y volcado a disco. Puede verse cómo se configura en su documentación.

Además se importan otras configuraciones que se usan dentro de este fichero y se da nombre dinámicamente al archivo de log utilizando algunas propiedades leídas de application.properties.

El código de este commit se encuentra en su repositorio.

Otros temas de interés:

19 de marzo de 2020

Entidades y Repositorios con JPA

Ya sabemos que nuestros datos deben ser persistidos para poder ser recuperados al arrancar nuestro sistema, ser compartidos, almacenados para análisis por parte de otras herramientas, etc...

Hasta ahora hemos visto cómo serializar nuestros datos y guardarlos en ficheros de nuestro disco. Si tenemos una gran cantidad de datos sería bueno que en vez de leer todos los datos y tratarlos en nuestra aplicación para seleccionar el que de verdad necesito (a lo mejor un cliente de entre millones) sería bueno usar un Sistema Gestor de Base de Datos (SGBD), que aparte de ofrecerme las típicas operaciones SQL puedo consumirlo como un servicio que atienda muchas peticiones a la vez. En cualquier caso, está claro que no vale simplemente con guardar en un fichero unos cuantos datos y que Spring tiene su solución.

Me voy a centrar en las Bases de Datos Relacionales ya que JPA está pensado para estas (quedan fuera del contenido las BD NoSQL)

¿Qué es JPA?

JPA es el acrónimo de Java Persistence API lo cual deja bastante claro de qué se ocupa: Es una API para Persistencia en Java. Para saber cómo utilizarlo hay un wikibook que explica bastante clara y concisamente su uso proporcionando ejemplos en las dos formas distintas que hay de definir cómo ejecutar la persistencia de nuestros datos.

JPA es la definición de una API pero no una implementación. De la misma forma que vimos en la entrada de logging, usamos una API que nos proporciona una fachada frente a sus implementaciones y de esta forma es sencillo cambiar la implementación usada por la aplicación. Existen varias implementaciones:
Visitando sus páginas se puede ver que dan soporte a muchos SGBD distintos, incluidos NoSQL. Hay que tener en cuenta que estas implementaciones cumplen JPA pero no son sólo eso.

Lo normal es que este tipo de herramientas proporcionen un mapeo entre objetos de aplicación y registros en SGBD relacional. De esta forma la aplicación es capaz de volcar nuestros datos en memoria a una BD y viceversa. Esto se conoce como Object-Relacional Mapping (ORM) y veremos su uso más adelante.

El sentido de tener JPA, ORM, Hibernate, etc... es que hay también mucho código boilerplate y es muy laborioso de implementar, probar y repetitivo. Sirva como ejemplo ese snipet básico con JDBC de una implementación para el supuesto con el que vamos a trabajar: tiene cientos de líneas de código, hace bastante poco y está muy ligado a un SGBD concreto.

Usando JPA vamos a persistir nuestros datos en cualquier SGBD con muy poco código, de manera fiable y con una facilidad increíble de cambiar de SGBD sin tener que modificar el código. Para guardar una entidad podremos hacerlo simplemente con dos anotaciones sobre ella y declarar una interface que extienda otra con una anotación sobre ella. Voy a usar un POJO simple para una clase Usuario que pondré, para simplificar este ejemplo, en el paquete es.lanyu.usuarios.repositorios pero no es obligatorio:
@Entity
public class Usuario {

    @Id
//  @GeneratedValue
//  int id;

    String nombre;

    String correo;

}
Simplemente las anotaciones @Entity y @Id definen que esta clase es una entidad y que su clave principal es el nombre de usuario.

Para guardar la entidad Usuario hay que crear un Repositorio que tendrá las operaciones CRUD típicas. Será mi interface UsuarioDAO y lo pongo en el mismo paquete que Usuario:
@Repository
public interface UsuarioDAO extends JpaRepository {}
Con sólo estas dos líneas tengo todas las operaciones CRUD y además otras cosas como paginación y consultas personalizadas.

Si hacemos memoria, cuando vimos @Component había tres especializaciones de ella, aquí estamos usando una de ellas: @Repository.

Al marcar esta interface con esa anotación la estamos haciendo autodetectable y puede ser escaneada para ser añadida como un bean.

Si ejecutamos el código en este punto no hará nada, de hecho ni siquiera será escaneado. Nos falta el encargado de hacer que todo esto funcione, que sepa con que BD debe conectar, las credenciales para hacerlo, la implementación a usar y otras configuraciones necesarias. El responsable de todo esto en JPA se llama EntityManager.

Hay varias formas de crearlo. Nosotros vamos a usar XML y voy a incluir también dónde están nuestros repositorios de JPA. Si se nos pasa el momento de susto al ver un XML con muchas cosas que no entendemos y nos centramos en lo importante, veremos que es prácticamente un copy & paste de este snipet.


NOTA: Estoy usando la capacidad que tenemos de configurar con propiedades el XML para mejorar la reutilización y simplificarlo, pero podría estar escrito el valor directamente.

Sólo tenemos que establecer nuestras propiedades para decir dónde están nuestros repositorios y nuestras entidades:
es.lanyu.entities-package=es.lanyu.usuarios.repositorios
es.lanyu.jpa-package=${es.lanyu.entities-package}
Ya dijimos que con Spring Boot eliminamos la necesidad de usar XML. La traducción a anotación de estas dos informaciones se puede hacer sobre nuestra clase de configuración:
Nos creamos un usuario para ver que todo funciona correctamente y se nos guarda. Para simplificar las pruebas lo genero en automático así que queda nuestro código del main así:
public static void main(String[] args) {
    ConfigurableApplicationContext context =
            SpringApplication.run(DatosdeportivosapiApplication.class, args);

    UsuarioDAO usuarioDAO = context.getBean(UsuarioDAO.class);
    usuarioDAO.save(generaUsuario());
    List<Usuario> usuarios = usuarioDAO.findAll();
    usuarios.stream().map(Usuario::toString).forEach(log::info);

    context.close();
}

static Usuario generaUsuario() {
    int numero = 10000;
    String usuario = "user" + ThreadLocalRandom.current().nextInt(numero, numero*20);
    return new Usuario(usuario, usuario + "@mail.com");
}

¿Qué operaciones tiene mi repositorio?

Tiene las que se pueden esperar de un CRUD. Usando el asistente de contenido no hacemos una idea. Lo bueno de es que JPA tiene una sintaxis para el nombre de sus métodos que implica un mapeo a una sentencia SQL sin tener que implementarla. El siguiente diagrama lo describe:


Fuente: Libro Spring in Action

Podemos ver las palabras clave y posibilidades de esta sintaxis en la documentación.

En nuestro caso vamos a recuperar todos los usuarios que contengan un texto que digamos en su correo:
List<Usuario> findByCorreoContaining(String txt);
Si usamos este nuevo método, reemplazando al findAll() anterior, podemos ver que nos devuelve ya sólo los registros que coincidan:
List<Usuario> usuarios = usuarioDAO.findByCorreoContaining("5");
Todavía queda mucho para dominar JPA, pero creo que si comparamos el ejemplo usando JDBC a usando JPA la diferencia es abismal y merece la pena el esfuerzo de aprenderlo y usarlo.

Puedes ver el código hasta este punto en su repositorio.

Lo siguiente que veremos es cómo hacer ese mapeo ORM sobre entidades a las que no tenemos acceso al código.

18 de marzo de 2020

Inyección de Dependencias - @Autowired

Ya tenemos nuestro contenedor preparado con los beans que hemos cargado de distintas formas y personalizados con configuración externa. Por ahora lo máximo que hemos inyectado es una cadena de texto recuperada del fichero de propiedades tipo clave-valor. Es algo muy bueno pero no explicaría la fama de Spring ni se está invirtiendo el control.

En esta sesión vamos a inyectar beans desde nuestro contenedor usando la anotación @Autowired.

En Spring podemos realizar Inyección de Dependencias (DI) de tres formas:



En el ejemplo se están usando las anotaciones de Spring para describir cómo se inyectará nuestro bean de tipo CustomerService en nuestra clase BankingService (por constructor para campos obligatorios, por propiedad para los no obligatorios y directamente en el campo que debe evitarse).

Empezamos haciendo DI por constructor en una clase nueva es.lanyu.datosdeportivosapi.TestAutowired sobre su campo testInyectado de tipo es.lanyu.Test:
import es.lanyu.Test;

@Component("autowired")
public class TestAutowired extends Test {

    Test testInyectado;

    public TestAutowired(es.lanyu.datosdeportivosapi.Test testPorConstructor) {
        testInyectado = testPorConstructor;
    }

}
Vamos a modificar nuestro método init() para mostrar el valor de testString para nuestro bean inyectado por consola (usando el log):
import es.lanyu.Test;

@Component("autowired")
public class TestAutowired extends Test {

    private final Logger log = LoggerFactory.getLogger(TestAutowired.class);

    ...

    @Override
    public void init() {
        log.warn("Texto de bean inyectada: {}", testInyectado.getTestString());
    }
}
Veremos que nos devuelve el texto "String por defecto" ¿Por qué?: si seguimos el flujo de la aplicación cuando construya el bean "autowired" inyectará el único bean que encaja.

¿Qué pasa si tenemos más de un constructor?

Se puede marcar el que se quiere usar o marcar los que se pueden usar. Spring buscará de entre los que puede resolver el que mayor coincidencia de parámetros encuentre:
@Autowired(required=false)
public TestAutowired() {}

@Autowired(required=false)
public TestAutowired(es.lanyu.datosdeportivosapi.Test testPorConstructor) {
    testInyectado = testPorConstructor;
}

¿Qué pasa cuando varios beans encajan?

Para desambiguar en este caso debemos usar la anotación @Qualifier("nombreBean"). Si queremos inyectar el tipo es.lanyu.Test tenemos 3 candidatos posibles. Vamos a usar el bean con nombre "test" para inyectarlo en un campo:
@Autowired
@Qualifier("test")
Test testInyectado;
Ahora tenemos en testInyectado el bean "test". Spring sigue un orden al hacer DI:
  1. Usar el constructor
  2. Asignar los campos
  3. Usar los métodos de configuración (setter en nuestro caso)
No se aconseja usar la inyección de campo para hacer DI y es preferible usar el constructor para valores obligatorios y métodos para los opcionales. Éstos últimos permiten trabajar con el valor fuera de Spring al contrario que el otro: ninguno de los miembros utilizados tienen por qué ser públicos.

¿Cómo se inyecta en un método que tiene varios parámetros?

Spring va a emparejar los beans con los tipos pedidos en los parámetros. En caso de tener que desambiguar también puede usarse @Qualifier en los parámetros:
@Autowired
private void setTestInyectado(@Qualifier("config") Test testPorSetter,
                              @Qualifier("test") Test testPorSetter2) {
    testInyectado = new Test();
    testInyectado.setTestString(testPorSetter.getTestString()
                              + " y " + testPorSetter2.getTestString());
    }
}

¿No puedo elegir un bean de un tipo por defecto?

Spring permite dar preferencia a un bean con la anotación @Primary:
@Bean
@Primary
public Test miTest() {...}
No obstante, no hay ningún control global sobre las anotaciones y pueden marcarse varias que sigan produciendo conflictos.

Puedes encontrar el código hasta aquí en su repositorio y ver el vídeo del webinar:

Con esto termina la parte básica de Spring. A partir de ahora nos centraremos en desarrollar un supuesto sobre una aplicación que gestiona datos deportivos.

17 de marzo de 2020

Logging en Spring

Hasta ahora hemos estado escribiendo mensajes en consola usando System.out.print*. Sin embargo para conocer el estado de una aplicación no siempre quieren sacarse todos los mensajes por consola. Se puede necesitar aplicar un filtro o realizar otras operaciones como:
  • Guardar en un fichero log en disco
  • Enviar un mensaje de correo
  • Hacer una llamada a un servicio web
  • Derivar a una cola de mensajes
  • Activar una sirena
  • Cualquier cosa mas que se te ocurra
Parece necesario un sistema que permita tomar distintas respuestas cuando se produce un mensaje. La idea de logging es disponer de un registro de mensajes organizados que puedan ser tratados en función de varios factores:
  • Gravedad del evento (Level)
  • Momento en que se produce (Date Time)
  • Componente origen del mensaje (Class del log)
  • En qué entorno se ejecute la aplicación (desarrollo, pruebas, producción...)
  • Mensaje que incluya
  • Otros
De aquí detectamos que sería bueno organizar esos mensajes conteniendo esa información y poder conocer el estado de un sistema en un determinado momento. Existen muchas librerías al respecto:
  • slf4j proporciona una interface única para varias implementaciones, lo que lo convierte en algo muy conveniente si se quiere cambiar la implementación en un futuro
  • La implementación nativa de java: java.util.logging
  • La del famoso log4j de apache
  • logback que la usa por defecto Spring Boot con slf4j
  • Y muchas otras más

Spring Boot ya dijimos que toma por defecto un conjunto de librerías por defecto en varios subsistemas de la aplicación. En el caso del logging Spring Boot utiliza por defecto slf4j con la implementación logback.

En esta entrada vamos a ver cómo personalizar el formato del log, trabajar con niveles y todo usando nuestras propiedades. Primero vamos a cambiar nuestra clase Test para que deje de usar System.out.println y use su log:
public class Test {
    private final Logger log = LoggerFactory.getLogger(Test.class);

    ...

    public void init() {
        log.info("Llamado init(): {}", testString);
    }
}
Si ejecutamos vemos que nuestro mensaje aparece igual de formateado que el resto de logs producidos por los otros componentes que usa Spring. Cuando queremos registrar un evento con nuestro logger disponemos de distintos niveles para marcar nuestro mensaje (en el ejemplo de arriba hemos usado el nivel INFO). El orden de prioridad de mayor a menor en slf4j es:
  1. ERROR
  2. WARN
  3. INFO
  4. DEBUG
  5. TRACE
NOTA: Estos niveles dependen de cada librería, no están unificados (ejemplo log4j)

Configurando el log

Ahora vamos a configurar cómo tratar el log con varias propiedades:
Niveles:
  • Se puede poner un nivel base para todos los logs (por defecto es INFO):
    logging.level.root=WARN
  • O especificar el nivel para un dominio concreto (por ejemplo para que los logs propios de nuestra aplicación tengan más detalle):
    logging.level.es.lanyu=DEBUG
Formato:
  • De fecha/hora: el formato de hora es muy fácil de cambiar como en este ejemplo (eliminamos los segundos):
    logging.pattern.dateformat=yyyy-MM-dd HH:mm
  • Del mensaje: se puede modificar el formato completo del mensaje con su propiedad. Puede ser distinto el formato de la consola y el archivo. En este ejemplo se cambia el patrón de logback para formatear la consola utilizando otra propiedad que nos hemos creado para dar el formato de la fecha/hora:
    lanyu.formatofecha=%date{ddMMM HH:mm:ss, UTC}Z
    logging.pattern.console=${lanyu.formatofecha} [%thread %clr(${PID:- })] %-5level %logger{15} => %msg %n
  • Color: que se puede aplicar a los elementos, quedando el formato anterior más enriquecido (necesita una consola que admita color):
    logging.pattern.console=${lanyu.formatofecha} [%thread %clr(${PID:- })] %highlight(%-5level) %cyan(%logger{15}) => %msg %n
Destino:
Ejecutando la aplicación en cada paso se puede ver cómo cambia el formato de todo el log con una sola línea en el fichero de propiedades.

Puedes encontrar el código hasta aquí en su repositorio y ver el vídeo del webinar:

En la siguiente entrada empezaremos a inyectar beans con la anotación @Autowired.

16 de marzo de 2020

Propiedades y @Value

Spring Boot nos permite externalizar configuraciones como ofrece en una de sus ventajas. De hecho, al crear el proyecto con Spring Initialzr, automáticamente tenemos un nuestra carpeta resources un fichero llamado application.properties. Para encontrar este fichero sigue un convenio por defecto.

Al encontrarlo fichero será leído automáticamente junto con otras muchas fuentes de propiedades posibles (este fichero aparece el número 15). Sigue el mismo principio general que cuando sobrescribimos el bean con el XML: cuanto más unida está la propiedad al código menos prioridad tiene para permitir cambios externos.

Por otro lado, tendremos que leer esas propiedades cargadas y Spring nos ayuda con su anotación @Value para poder marcar un campo o un parámetro en el constructor e inyectar así el valor de una propiedad. El valor recuperado de una propiedad será un "String" que puede ser convertido por Spring para adaptarse al tipo de la variable siguiendo una sintaxis concreta.

Siguiendo con nuestro ejemplo vamos a ponerle un valor a nuestro testString que provenga de una propiedad. Para ello tenemos que poner nuestra propiedad en el fichero application.properties:
datos-deportivos.testString=String desde "application.properties"
Y luego inyectarlo en el campo:
@Value("${datos-deportivos.testString}")
String stringPropiedad;
Con esto se colocará el valor que tenga esa clave de entre todas las fuentes de propiedades y de forma priorizada (en nuestro caso sólo hay una fuente: application.properties)

Para ver ese valor habrá que sobrescribir el método init() pues no servirá querer asignárselo en el constructor (si se prueba se verá que el resultado tras construir el bean sería null). Vamos entonces a usar ese valor:
public void init() {
    testString = stringPropiedad;// System.out.println(stringPropiedad);
    super.init();
}
Por supuesto si hubiera un cambio en stringPropiedad afectaría al valor de testString. En este caso sería mejor inyectarle directamente este valor por el constructor añadiéndolo como parámetro. Primero voy a crearme otra propiedad distinta para diferenciarla de la anterior:
valor.parametro=String por constructor
Y ahora anoto con @Value al parámetro que quiero que tenga ese nuevo valor:
public Test(@Value("${valor.parametro}")// Para usarlo en constructor tiene que ser por parametro
            String stringPropiedad) { // Usa el constructor sin parametro si tambien existe
    System.out.println(stringPropiedad);
    testString = stringPropiedad;
}
De esta forma se asigna el valor de la propiedad al campo testString y el método init() se utiliza sin ningún cambio (ahora se puede ver que sí se dispone del valor dentro del constructor).

No es necesario tener todas las propiedades en un sólo fichero, de hecho sabemos que hay varias fuentes. De esta forma tiene sentido tener ficheros de propiedades para distintos aspectos de nuestra aplicación como el logging, el acceso a datos, credenciales o cambios para distintos entornos. Ahora voy a mover la propiedad valor.parametro a otro fichero nuevo en la misma carpeta que application.properties con el nombre application2.properties. Si ejecuto el código ahora veré que no es capaz de encontrar esa clave, pero se puede añadir un fichero a mi aplicación para contar también con sus propiedades simplemente marcándola con la anotación @ProperySource:
...
@PropertySource({"application2.properties"})
public class DatosdeportivosapiApplication { ... }
NOTA: @PropertySource no es válido para las propiedades que sean de logging ya que el logging se establece antes del ApplicationContext.

Ejecutándolo se va a ver todo correcto.

Aunque @Value parezca un recurso muy potente hay que usarlo con mesura dado que un cambio en un proyecto grande puede obligarnos a recorrer todo el proyecto en busca de una propiedad y además: "no deja de ser un simple String", con los errores tipográficos que puede conllevar. Para eso existen otras aproximaciones que aseguran un tipo, pero queda fuera de lo que se pretende en esta entrada.

Puedes obtener el código hasta este punto en su repositorio.

Lo siguiente será utilizar esta configuración para ver que es el logging, usarlo y personalizarlo con propiedades.

15 de marzo de 2020

Configuración Java: Anotaciones @Configuration y @Bean

Hasta ahora hemos visto dos formas de añadir dependencias pero recordemos que hay tres. En esta entrada vamos a ver la que nos queda: Configuración Java.

Para ello vamos a usar tres cosas:
  1. La primera ya la hemos visto: el escaneo de componentes. Nos vamos a crear una clase de configuración llamada JavaConfig (el nombre no tiene porqué ser este y podemos tener varias clases de configuración). Esta clase estará el en paquete es.lanyu.datosdeportivosapi para que sea escaneada por Spring
  2. La segunda hemos visto a un hermano suyo: marcar la clase para ser autodetectada. En este caso en vez de usar la anotación @Component usamos la anotación @Configuration, pero el concepto es el mismo.
  3. La tercera es otra anotación que sirve para indicar que el objeto devuelto en un método es un bean que debe ser cargado en el contenedor: @Bean. Además voy a añadir el nombre "config" a esta anotación para desambiguar el bean. Se hace igual que hicimos con @Component.
El código con todo esto quedaría:
package es.lanyu.datosdeportivosapi;

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

@Configuration
public class JavaConfig {

    @Bean(name="config")
    public Test miTest() {
        Test test = new Test() {
            int llamadas = 0;
            
            @Override
            public void init() {
                System.out.printf("Llamado %s veces", ++llamadas);
                System.out.println();
            }
            
        };
        test.init();
        
        return test;
    }
}
NOTA: también puede ponerse simplemente @Bean("config")

He sobrescrito el método init() para que quede claro que por defecto los beans son singleton y que se crea un objeto que es cargado en nuestro contenedor y se devuelve siempre el mismo, no se genera uno nuevo usando su código para generarlo. Así cuando recuperamos el bean con nombre "config" y volvemos a llamar al método init() se ve claramente que es la segunda vez que se le llama, lo que demuestra que me está devolviendo el mismo objeto que se creó:
test = context.getBean("config", Test.class);
test.init();}
La salida por consola es:

...
Llamado 1 veces
...
Llamado 2 veces
...

Puedes encontrar el código hasta este punto en su repositorio y ver el video del webinar.


Lo siguiente que haremos será pasar a usar Spring Boot y veremos cómo se hace todo lo hecho hasta ahora usándolo.

14 de marzo de 2020

Spring Boot - ¿Qué es?

Siguiendo el ejemplo de la entrada anterior, inicialmente nos hicimos un proyecto Spring utilizando Spring Initialzr, pero después nos centramos en construir nuestro contenedor de dependencias básico. Para ello comentamos la anotación @SpringBootApplication y ejecutamos nuestra aplicación de una forma más antigua, sin usar la línea que nos venía por defecto SpringApplication.run(DatosdeportivosapiApplication.class, args);. Quitamos esas líneas porque esos dos tipos pertenecen a Spring Boot.

También añadimos una configuración por XML para activar el escaneo de componentes, hay más configuraciones que se pueden añadir de la misma forma y muchas se han mostrado repetitivas para la mayoría de proyectos. Se trata de un código boilerplate que podemos evitar con la ayuda de Spring Boot.

Spring Boot va a proporcionarnos un proyecto Spring con ciertas decisiones de configuración ya adoptadas que nos hará la vida más cómoda añadiendo varias anotaciones extras, entre otras cosas y que nos proporcionará las siguientes ventajas:
  1. Crear aplicaciones autónomas
  2. Proporcionarnos nuestro servidor de aplicaciones embebido (no WAR)
  3. Proveer de potentes librerías de terceros ya integradas y compatibles (no lidiar con problemas de versionado entre dependencias)
  4. Características como métricas, monitorización y configuración externalizada
  5. No hace falta configuraciones XML
El código final del ejemplo anterior quedaría resumido en:
@SpringBootApplication
@ImportResource("config.xml")
public class DatosdeportivosapiApplication {

  public static void main(String[] args) {
    ConfigurableApplicationContext context =
            SpringApplication.run(DatosdeportivosapiApplication.class, args);

    Test test;
    
//    test = context.getBean(Test.class);
//    test = context.getBean("test", Test.class);
//    test = context.getBean("anotacion", Test.class);
    test = context.getBean("config", Test.class);
    test.init();
    System.out.println(test.getClass().getName());
    
    context.close();
  }
}
Fijarse que ni siquiera es necesario tener el fichero "config-scan.xml" que usábamos para activar el escaneo de componentes, ni otras configuraciones que todavía no nos han hecho falta.

NOTA: Por ejemplo, las anotaciones están deshabilitadas por defecto, para habilitarlas habrá que añadir la línea <context:annotation-config /> a nuestra configuración (applicationContext.xml) junto con los paquetes que hay que escanear. Es habitual usar Spring Boot en vez de simplemente Spring pues Spring Boot trae las típicas configuraciones añadidas para facilitarnos la vida y una de ellas es esta.

En éste vídeo se ven otras anotaciones útiles al mismo nivel que @ImportResource explicando cómo usarlas en nuestro ejemplo.


El código hasta el punto actual se puede encontrar en su repositorio. En la siguiente entrada vamos a ver cómo añadir propiedades (clave = valor) y usarlas dentro de nuestro código en ejecución.

Desambiguar entre dos beans

Por ahora sólo hemos tenido cargado un bean que cumpla con la clase que pedimos de tipo Test bien porque sólo cargábamos un bean de ese tipo o porque lo sobrescribíamos.

La solicitud de un bean en general no se va a hacer explícitamente solicitándolo con getBean(). Lo normal es que en nuestro código haya partes que necesitan de un objeto que nos proporcione una funcionalidad determinada y que lo ofrezca una clase concreta o una implementación de una interface. Casos típicos son objetos DAO, serializadores o servicios por ejemplo.

En estos casos nuestro código tendrá definida una dependencia y Spring se encargará de satisfacerla con sus beans si le hemos dado las herramientas suficientes (hemos definido los beans que cubren esa necesidad en nuestro contenedor). Cómo definir está dependencia lo veremos más adelante cuando veamos las distintas formas de inyectar dependencias.

Ahora mismo lo que debe quedarnos claro es que al definir una dependencia puede ocurrir que haya varios beans que la cubran y entonces Spring no sabrá cuál tiene que usar si no le ayudamos. La forma de hacerlo será poniendo un nombre que distinga a nuestro beans. Todos los beans van a tener un nombre generado si no se lo ponemos pero es mejor ponérselo si puede haber conflicto (normalmente no lo habrá: seguramente no tendremos dos DAO/Repository para un mismo tipo o el mismo servicio implementado y cargado dos veces).

Hemos visto dos formas de definir nuestros bean y en cada una se hace de una forma:
  1. Se puede poner el nombre como atributo XML en la definición de nuestra etiqueta bean por XML
    <bean class="es.lanyu.Test" id="test" ...>
    
  2. Si usamos la anotación @Component se puede añadir su nombre como valor entre paréntesis a continuación de esta anotación
    @Component("anotacion")
    public class Test extends es.lanyu.Test {...}
    
Ahora para probar el código forzamos la carga de dos beans distintos que cumplan con el tipo Test. Si siguiéramos usando la invocación con sólo el tipo nos saldría la excepción:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'es.lanyu.Test' available: expected single matching bean but found 2: anotacion,test

Pero vamos a usar la sobrecarga del método getBean(String, Class) que incluye el nombre además del tipo de bean que usamos. De esta forma podemos recuperar uno u otro bean usando el nombre para desambiguarlos ("test" o "anotacion").
test = context.getBean("test", Test.class);
test = context.getBean("anotacion", Test.class);
Puedes encontrar el código hasta aquí en su repositorio de GitHub y ver el video del webinar.


En la siguiente entrada vamos a ver la tercera forma de crear un bean: Configuración por Java.

13 de marzo de 2020

Cambiar el paquete base para escanear

En la entrada anterior hemos visto que por prelación de Spring se descartan o sobrescriben definiciones de beans menos prioritarias. En esta entrada vamos a cambiar el paquete base para escanear componentes Spring y veremos que ya no se duplica la definición del bean Test.

Modificamos nuestro fichero config-scan.xml para establecer el paquete base desde es.lanyu a es.lanyu.datosdeportivosapi. Se pueden establecer también más de un paquete base. Es una buena práctica escanear sólo los paquetes con las definiciones que queremos incluir en nuestro contenedor: evitamos un trabajo innecesario y no se nos colará una definición descontrolada. Establecer un paquete "raíz" como paquete base para que Spring escanee todo nuestro código es señal de programador perezoso.

Nuestra línea modificada quedaría así:
<context:component-scan base-package="es.lanyu.datosdeportivosapi"/>
Si ejecutamos el código ahora vemos que ya no se detectan 7 beans en este fichero de configuración, sólo se detectan 6 porque el paquete al que pertenece Test está fuera de nuestro paquete base.

Puedes encontrar el código en este punto en su repositorio y ver el video del webinar en este momento.


En la siguiente entrada añadiremos otro bean en el paquete que se está escaneando actualmente y tendremos un conflicto: dos beans cumplirán la dependencia de tipo es.lanyu.Test)

12 de marzo de 2020

Sobrescribir un bean con XML

En la entrada anterior hemos sustituido un fichero de configuración (para crear un bean por XML) por otro (realizar escaneo de @Component). ¿Qué pasaría si usaramos los dos a la vez?

En ese caso tendríamos dos bean asociados con un mismo tipo. Los bean por defecto son objetos Singleton y al definir el bean va a haber dos definiciones distintas (Test por XML y por @Component). Para este caso Spring tiene un orden de prioridad que descartará definiciones menos prioritarias: XML > Componentes escaneados.

Simplemente juntando las dos configuraciones vamos a ver en la consola lo siguiente:
  • Si ponemos primero nuestro fichero config-scan.xml se crean 7 beans (nuestra sólo es una, la que se llama "test", las otras 6 son propias de Spring) y se sobrescribe 1 sólo que es la nuestra porque es la repetida.
  • Si invertimos el orden a la hora de crear el contenedor veremos lo contrario: se detecta 1 bean, el nuestro, y luego se añaden otros 6 más, descartando el bean repetido menos prioritario.

Este sería el código para el primer ejemplo donde por consola se puede ver cómo se hace esta sobrescritura:
ConfigurableApplicationContext context = 
        new ClassPathXmlApplicationContext(
                   new String[]{"config-scan.xml", "config.xml"});
Por consola las líneas clave son estas tres:
19:25:33.268 [main] DEB...XmlBeanDefinitionReader - Loaded 7 bean definitions from class path resource [config-scan.xml]
19:25:33.299 [main] DEB...DefaultListableBeanFactory - Overriding bean definition for bean 'test' with a different definition: replacing [Generic bean: class [es.lanyu.Test]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [E:\Desarrollo\DatosDeportivos2020\Spring-contenedor-dependencias\build\classes\java\main\es\lanyu\Test.class]] with [Generic bean: class [es.lanyu.Test]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=init; destroyMethodName=null; defined in class path resource [config.xml]]
19:25:33.299 [main] DEB...XmlBeanDefinitionReader - Loaded 0 bean definitions from class path resource [config.xml]
Esto tiene todo el sentido del mundo: Los beans definidos por anotaciones están altamente acoplados al código, probablemente no tendremos acceso al código fuente o no queramos modificarlo pues es una librería de un tercero que no mantenemos nosotros. Por el contrario, la configuración XML está completamente desacoplada y es la única forma que tendríamos de modificar la configuración de un bean que no vamos a modificar por código. Por esta razón la configuración XML prevalece sobre el resto.

Puedes ver el código en este punto en el repositorio de GitHub y el video del webinar en este momento.


En la siguiente entrada vamos a ver cómo desambiguar dos beans que cubren una misma dependencia.

Anotaciones @Component

En la entrada anterior vimos como añadir un bean al contenedor de dependencias usando un fichero XML. En esta entrada vamos a cargar un bean usando la anotación @Component y pidiéndole Spring que escanee nuestros paquetes buscando esta anotación.

Cuando me refiero a la anotación @Component quiero referirme a ella o cualquiera de sus especializaciones como se puede ver en la siguiente figura.



Lo importante de ellas es que estas anotaciones permiten su autodetección y van a ser una forma muy fácil de añadir beans a nuestro contenedor de dependencias.

Estás anotaciones se usan a nivel de tipo y marcarán una clase para ser usada como definición de un bean de ese tipo.

En nuestro ejemplo la usamos sobre nuestra clase Test:
import org.springframework.stereotype.Component;

@Component
public class Test {
    ...
}
Si sólo marcamos un tipo con @Component no servirá de nada si no escaneamos el paquete que la contiene en su búsqueda. Por defecto las anotaciones y el escaneo de componentes están desactivados en Spring así que tendremos que configurar nuestra aplicación Spring para activarlo. Esto se hace con un fichero de configuración de la misma forma que usamos para crearnos el bean de tipo Test.

Para ello creamos un nuevo fichero de configuración al que llamaremos config-scan.xml y que contendrá el siguiente código:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="es.lanyu"/>

</beans>
Hay que fijarse en el paquete base que estamos declarando (en este caso base-package="es.lanyu"). Con esto le decimos a Spring que empiece a buscar anotaciones del tipo @Componente en el paquete base y todos los que están incluidos en él.

Para añadir esta configuración a nuestra aplicación modificamos la línea que creaba nuestro contenedor para que lea este fichero de configuración en vez del anterior (que tenía el nombre config.xml)
ConfigurableApplicationContext context = 
        new ClassPathXmlApplicationContext(
                   new String[]{"config-scan.xml"});
Ejecutándo ahora veremos que nos está creando un objeto de tipo Test con el valor por defecto en vez del valor que le habíamos puesto en nuestro fichero config.xml

Puedes encontrar el código hasta aquí en su repositorio de GitHub y ver el vídeo del webinar en este momento.


En la siguiente entrada vamos a ver qué pasa si definimos un bean del mismo tipo de dos formas distintas.

10 de marzo de 2020

Spring Framework - Lo básico

Spring es un framework con muchos años de vida, robusto y muy utilizado para ejecutar aplicaciones web.

Esta formado de numerosos módulos como se ve en la figura:



Spring 5 parte como base del JDK 8 y Jakarta EE 7 con lo que no funcionara en entornos anteriores.

La parte más fundamental de Spring (Core) es un contenedor (llamado IoC Container) con objetos que serán suministrados cuando se necesite un objeto que cumpla con unas condiciones determinadas (por ejemplo implementar una interface). Este patrón se conoce como Inyección de Dependencias (DI). Vamos a ver cómo se añaden esos objetos a ese contenedor y cómo se solicitan después.

Los objetos que se añaden al contenedor reciben el nombre de beans. Hay tres formas de añadir beans al contenedor:
  1. Mediante ficheros XML utilizando la etiqueta
  2. Usando Configuración Java, con la anotación @Bean
  3. Por Anotaciones, utilizando la anotación @Component entre otras
Las dos primeras se conocen como configuraciones explicitas mientras que las anotaciones se conocen como implícitas. Este esquema lo muestra de forma gráfica:



¿Entonces cuál de las tres formas es mejor?

Aunque todas pueden usarse con sus diferentes pros y contras, a lo largo del curso voy a centrarme bastante en la declaración por ficheros XML. Escojo esta forma por los siguientes motivos:
  1. Es la más flexible ya que desacopla completamente la configuración del código. Simplemente reemplazando nuestro fichero XML la aplicación funcionaría con la nueva configuración sin recompilar.
  2. Al desacoplar el código de la configuración no hace falta tener acceso a él o modificarlo. Esto no sería posible con las anotaciones ya que se deben modificar los miembros anotados.
  3. Hay escasa documentación, así que voy a explicar la dispersa información que he recopilado. ¡Y encima en español!
No obstante habrá ejemplos para todas las formas para saber elegirlas en función de las ventajas e inconvenientes en cada caso.

¿Cómo cargar una dependencia por XML?

Lo primero que debemos hacer es cargar el contenedor con los beans que necesitemos. En esta ocasión vamos a comentar el código relacionado con SpringBoot para cargar beans sin él. Posteriormente se verá la diferencia usándolo. La configuración para la creación del contenedor puede estar repartida en varios ficheros XML como en este ejemplo:
ApplicationContext context = 
    new ClassPathXmlApplicationContext(
                   new String[]{"configfile1.xml",
                                "configfile2.xml"});
Vamos a declarar por XML nuestro primer bean poniendo el siguiente código en un fichero llamado config.xml que pondremos en nuestra carpeta src/main/resources:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- bean definitions here -->
    <bean class="es.lanyu.Test" id="test" init-method="init">
        <property name="testString" value="¡He sido inyectada por XML!"></property>
    </bean>

</beans>
Una vez que está en el contenedor ya podemos recuperarlo buscándolo en el contexto de la aplicación por su nombre o clase con este código genérico:
UsefulClass usefulClass =  (UsefulClass) context.getBean("myBeanName");
UsefulClass usefulClass = context.getBean(UsefulClass.class);
En nuestro caso vamos a recuperarlo usando la clase Test y llamamos al método init.

De todas formas, no se recomienda solicitar los beans de esta forma cuando se necesitan sino inyectarlos, como veremos más adelante, con otros mecanismos que tiene Spring. En este ejemplo lo usamos para probar que el bean se ha creado y nuestro contenedor nos lo suministra.

En GitHub puedes ver un ejemplo de todos los pasos hasta aquí y ver el video del webinar.


En la siguiente entrada vamos a ver las otras formas de crear un bean.

Compárteme

Entradas populares