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.

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares