Mapeo Objetos / Relacional
Programación con Herramientas Modernas
El modelo de objetos y el relacional son diferentes
Los inconvenientes que surgen al persistir la información de objetos al modelo relacional se conocen como impedance mismatch, y las vamos a explicar en las diapositivas que siguen.
En el modelo relacional solo hay datos
Las funciones o stored procedures no están asociadas a una entidad, y la información en crudo puede también ser difícil de entender si no contamos con una documentación extra.
¿?
Consecuencias de almacenar solo datos
En el ejemplo, está claro que una interfaz stateless no tiene sentido persistirla. ¿Pero cómo mapeamos la referencia?
La pesada herencia
Asociar tabla = clase se dificulta cuando tenemos una jerarquía de objetos.
Referencias static/companion object
No podemos almacenar información relativa a una clase en la tabla (solo permite actualizar registros). Debemos crear entidades específicas (parámetros del sistema) para guardar esa información.
Ahora sí, comencemos a mapear
El recorrido va a incluir estos tópicos:
Identidad
En el modelo de objetos nosotros tenemos al menos una referencia al objeto. En el modelo relacional necesitamos identificarlo unívocamente mediante la PRIMARY KEY (subrogada o natural), entonces es muy probable que mi modelo de objetos se vea afectado (agregando por ejemplo un atributo extra).
Dominio / Conversiones de tipos
Cardinalidad: one-to-many
Si un pedido tiene muchos ítems, en el modelo de objetos tengo una colección de ítems en el pedido.
Pero en el modelo relacional, no está permitido tener atributos multivaluados, entonces la entidad hija (many) debe agregar una clave foránea hacia el identificador de la entidad madre (one).
Cardinalidad: one-to-many (continuación)
Vemos cómo implementar que un pedido tiene muchos ítems
En la opción A) la entidad Item tiene una clave subrogada autoincremental y el identificador del pedido no forma parte de la clave.
Cardinalidad: one-to-many (continuación)
En la opción B) la clave foránea de Pedido forma parte de la clave primaria del Item
Tipos de colecciones
Set: sin duplicados, no hay orden
Bag: admite duplicados, no hay orden
Lista: admite duplicados, hay orden
Ordered Set: sin duplicados, hay orden
Mapas clave / valor: acceso por clave, las claves no se repiten
¿Qué preguntas hacer para definir una colección?
Modelando una fila del supermercado
Necesitamos agregar en la base un campo orden, para poder recuperar la lista de la misma manera que la creamos.
Resumen del mapeo de colecciones
Cardinalidad: many-to-one
La relación many-to-one es similar a la one-to-many, en la entidad hija (many) agregamos la foreign-key a la entidad madre (uno o varios campos, dependiendo de si la clave es simple o compuesta).
one-to-many vs. many-to-one
Para el modelo relacional, no parece haber demasiado diferencia. En el modelo de objetos, no obstante, eso depende del origen de la relación (si un pedido tiene muchos ítems, tendremos una colección, pero si la relación es unidireccional y va solo de Item a Pedido, se pierde una forma de navegar el grafo).
Cardinalidad: many-to-many
Si un vendedor vende muchos productos y un producto tiene muchos vendedores, en objetos puede modelarse como una relación bidireccional, tengo dos colecciones distintas:
Cardinalidad: many-to-many (continuación)
En el modelo relacional, el DER lógico admite relaciones many-to-many de la siguiente manera:
Cardinalidad: many-to-many (continuación)
Pero a la hora de implementarlo, la 1FN nos recuerda que “nada de atributos multivaluados”. Entonces debemos crear una entidad asociativa:
Cardinalidad: many-to-many (continuación)
Surge de la relación many-to-many pero además incorpora información propia
Cardinalidad: many-to-many (continuación)
Podemos incorporar relaciones bidireccionales en objetos, eso requiere mantener la información sincronizada (implica mayor esfuerzo).
Cardinalidad: many-to-many (continuación)
Por el contrario, el esquema relacional permite la navegabilidad en ambos sentidos, por la naturaleza de las claves foráneas:
Subtipos / Herencia
El DER lógico admite la definición de subtipos, como ocurre para cada uno de los productos en el ejemplo original:
Subtipos / Herencia (continuación)
Pero la implementación física de los subtipos requiere la adaptación, y veremos que hay tres estrategias para implementarlas:
Subtipos / Herencia (continuación)
Pero la implementación física de los subtipos requiere la adaptación, y veremos que hay tres estrategias para implementarlas:
Subtipos / Herencia (continuación)
Pero la implementación física de los subtipos requiere la adaptación, y veremos que hay tres estrategias para implementarlas:
Estrategia Mapeo Herencia Table per class
Tenemos una entidad por cada clase concreta, lo que en nuestro ejemplo podría implementarse como:
Estrategia Mapeo Herencia Table per class (cont.)
En la mayoría de los frameworks de mapeo, en lugar de tener una FK por cada entidad concreta, se trabaja con un único campo. La desventaja: perdemos la FK y dentro de la base no podemos garantizar que haya consistencia (que no apunte a un identificador inválido, por ejemplo)
Estrategia Mapeo Herencia Table per class (cont.)
Vemos cómo se implementan ambas variantes para
Estrategia Mapeo Herencia Table per class (cont.)
Estrategia Mapeo Herencia Joined
Una tabla por cada clase, incluyendo a las abstractas. Trata de normalizar el modelo de objetos.
Estrategia Mapeo Herencia Joined (cont.)
Estrategia Mapeo Herencia Single Table
Toda la jerarquía se mapea a una sola tabla.
Estrategia Mapeo Herencia Single Table (cont.)
Análisis comparativo de las soluciones
Queremos conocer los productos de un proveedor dado que pesen más de 1 kg
SELECT columnas� FROM PRODUCTOS prd� WHERE prd.PESO > 1� AND prd.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR
SINGLE TABLE
Análisis comparativo de las soluciones
Queremos conocer los productos de un proveedor dado que pesen más de 1 kg
SELECT columnas� FROM CONSERVADOS con� WHERE con.PESO > 1� AND con.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR� UNION�SELECT columnas� FROM COMPRADOS com� WHERE com.PESO > 1
AND com.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR� UNION�SELECT columnas� FROM FABRICADOS fab� WHERE fab.PESO > 1� AND fab.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR
TABLE PER CLASS
como a priori no sabemos de qué instancia concreta se trata, necesitamos hacer un UNION de las 3 tablas concretas (recordemos que en un UNION deben coincidir las columnas y los tipos de cada consulta)
Análisis comparativo de las soluciones
Queremos conocer los productos de un proveedor dado que pesen más de 1 kg
SELECT columnas� FROM PRODUCTOS prd � LEFT JOIN CONSERVADOS CON� ON CON.PRODUCTO_ID = prd.ID � LEFT JOIN COMPRADOS COM� ON COM.PRODUCTO_ID = prd.ID � LEFT JOIN FABRICADOS FAB� ON FAB.PRODUCTO_ID = prd.ID� WHERE prd.PESO > 1� AND prd.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR
JOINED
como a priori no sabemos de qué instancia concreta se trata, necesitamos hacer un LEFT JOIN a cada subclase concreta
INNER JOIN vs. LEFT JOIN
create table personas
(id INT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL);
create table perros
(id INT PRIMARY KEY,
apodo VARCHAR(100) NOT NULL,
persona_id INT NULL,
foreign key (persona_id)
references personas(id));
insert into personas values (1, 'catalina');
insert into perros values (1, 'firulais', 1);
insert into perros values (2, 'bobby', null);
select *
from perros p
join personas d
on p.persona_id = d.id;
select *
from perros p
left join personas d
on p.persona_id = d.id;
Análisis comparativo: relaciones -one
Supongamos ahora que queremos incorporar al modelo cuál es el producto estrella de cada proveedor.
Análisis comparativo: campo discriminator
Análisis comparativo: performance
Proceso de hidratación
DB
Proceso de hidratación (cont.)
¿Qué query me conviene para poder resolver esta interfaz de usuario?
Proceso de hidratación (cont.)
¿Qué query me conviene para poder resolver esta interfaz de usuario?
SELECT * � FROM PEDIDOS p,� JOIN CLIENTES c � ON p.CLIENTE_ID = c.ID
WHERE FECHA_PEDIDO BETWEEN :FechaDesde AND :FechaHasta
Proceso de hidratación (cont.)
¿Qué query me conviene para poder resolver esta interfaz de usuario?
SELECT * � FROM PEDIDOS p,� JOIN CLIENTES c � ON p.CLIENTE_ID = c.ID
WHERE FECHA_PEDIDO BETWEEN :FechaDesde AND :FechaHasta
Cliente
Lista de items
1.215.259
Pedido
BASF SA
Proceso de hidratación (cont.)
En cambio, si selecciono una factura para ver el detalle...
Proceso de hidratación (cont.)
En cambio, si selecciono una factura para ver el detalle...
Cliente
Lista de items
1.215.259
Pedido
BASF SA
Item
Item
Producto
100
1
Producto
Lazy association a través de un objeto proxy
La colección de ítems del pedido es lazy, esto significa que por defecto solo traemos información del pedido y el cliente (el JOIN se hace automático porque la relación es many-to-one).
Se implementa mediante un proxy, un objeto que simula ser la lista de ítems, y cuando se envía el primer mensaje se dispara la consulta a la base para traer la información que falta (necesitamos una sesión activa)
Lazy association a través de un objeto proxy (cont.)
Por ejemplo, si tenemos la sesión contra la base de datos aún abierta y se envía el mensaje montoTotal a pedido:
class Pedido {
...
fun itemMasCaro() = items.maxOf { it.precioTotal() }
esto provoca que Hibernate trate de obtener la lista de ítems asociados a dicho pedido. Si nadie envía un mensaje a los ítems, nada ocurre.
Ciclo de vida de los objetos
Hay objetos cuyo ciclo de vida está atado a otros: en ese caso debemos tener actualizaciones y eliminaciones en cascada. En otros casos, como en el segundo, tienen ciclos de vida independientes.
Data Mapper vs. Active Record
En la cursada vamos a modelar un objeto DAO (Data Access Object, en Springboot llamado Repository): la responsabilidad de definir queries estará en un objeto separado del dominio. Esto se conoce como Data Mapper. La otra variante que pueden investigar es Active Record (hay un ejemplo en Rails), donde al objeto de dominio se le inyectan métodos save(), delete(), etc.
Data Mapper
Active Record
Mapeo manual vs. Frameworks OR/M
El mapeo manual nos da más control, pero también nos obliga a definir muchas cosas. Los frameworks ayudan a tomar decisiones en forma más declarativa.
¿O -> R o R -> O?
La preponderancia del modelo de objetos o el relacional dependerá de muchos factores: si estamos mapeando un sistema existente (legacy), si nuestra aplicación hace principalmente transformación de datos (el juego está más del lado relacional), o si por el contrario la base de datos es únicamente donde descansa la persistencia de nuestra información.
Preguntas