1 of 57

Mapeo Objetos / Relacional

Programación con Herramientas Modernas

2 of 57

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.

3 of 57

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.

¿?

4 of 57

Consecuencias de almacenar solo datos

En el ejemplo, está claro que una interfaz stateless no tiene sentido persistirla. ¿Pero cómo mapeamos la referencia?

5 of 57

La pesada herencia

Asociar tabla = clase se dificulta cuando tenemos una jerarquía de objetos.

6 of 57

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.

7 of 57

Ahora sí, comencemos a mapear

El recorrido va a incluir estos tópicos:

  • Identidad
  • Dominio / Conversiones de tipo
  • Cardinalidad / Relaciones de asociación: One to many - Many to one - Many to many
  • Subtipos / Herencia
  • Hidratación Lazy / Eager / Deshidratación
  • y otras cuestiones

8 of 57

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).

9 of 57

Dominio / Conversiones de tipos

  • El String de Java no tiene limitaciones => VARCHAR o CHAR requiere un tamaño fijo
  • Enums pueden almacenar solo valores o tener una tabla especial
  • Los números enteros o con decimales requieren también especificar una precisión en la base
  • El tipo de dato booleano se suele transformar a 0 / 1
  • Las infaltables fechas
  • Las constraints de SQL son populares para asegurar consistencia de mis datos (por ejemplo que el saldo sea siempre positivo, o que un String esté en un rango de valores permitidos), no tanto las invariantes en el modelo de objetos (hay pocos lenguajes que lo soportan: Eiffel, D, el framework Java Modeling Language de Java)

10 of 57

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).

11 of 57

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.

12 of 57

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

13 of 57

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

14 of 57

¿Qué preguntas hacer para definir una colección?

15 of 57

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.

16 of 57

Resumen del mapeo de colecciones

17 of 57

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).

18 of 57

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).

19 of 57

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:

20 of 57

Cardinalidad: many-to-many (continuación)

En el modelo relacional, el DER lógico admite relaciones many-to-many de la siguiente manera:

21 of 57

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:

22 of 57

Cardinalidad: many-to-many (continuación)

Surge de la relación many-to-many pero además incorpora información propia

23 of 57

Cardinalidad: many-to-many (continuación)

Podemos incorporar relaciones bidireccionales en objetos, eso requiere mantener la información sincronizada (implica mayor esfuerzo).

24 of 57

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:

25 of 57

Subtipos / Herencia

El DER lógico admite la definición de subtipos, como ocurre para cada uno de los productos en el ejemplo original:

26 of 57

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:

  • SINGLE TABLE

27 of 57

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:

  • SINGLE TABLE
  • TABLE PER CLASS

28 of 57

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:

  • SINGLE TABLE
  • TABLE PER CLASS
  • JOINED

29 of 57

Estrategia Mapeo Herencia Table per class

Tenemos una entidad por cada clase concreta, lo que en nuestro ejemplo podría implementarse como:

30 of 57

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)

31 of 57

Estrategia Mapeo Herencia Table per class (cont.)

Vemos cómo se implementan ambas variantes para

  • Pedido 1, dos ítems
    • 2 unidades de Queso Camembert (producto fabricado)
    • 1 unidad de Queso Gruyère (producto conservado)
  • Pedido 2, dos ítems
    • 7 unidades de Jamón Cocido (producto comprado)
    • 3 unidades de Queso Camembert (producto fabricado)

32 of 57

Estrategia Mapeo Herencia Table per class (cont.)

  • Las entidades que surgen de las subclases de producto repiten los atributos definidos en la superclase
  • El nombre de la entidad sirve para saber a qué clase concreta representa cada registro

33 of 57

Estrategia Mapeo Herencia Joined

Una tabla por cada clase, incluyendo a las abstractas. Trata de normalizar el modelo de objetos.

34 of 57

Estrategia Mapeo Herencia Joined (cont.)

  • No necesita duplicar atributos en las tablas
  • Es el modelo que reproduce más fielmente el modelo de objetos
  • Permite trabajar con una FK de Item a Producto
  • Generar un nuevo producto requiere 2 inserts, y se necesita hacer JOIN siempre para obtener la información de un producto

35 of 57

Estrategia Mapeo Herencia Single Table

Toda la jerarquía se mapea a una sola tabla.

36 of 57

Estrategia Mapeo Herencia Single Table (cont.)

  • Item conserva su FK hacia la tabla de Productos
  • Como cada subclase puede tener diferentes atributos, es necesario admitir campos nulos
  • No requiere JOIN con ninguna otra tabla
  • Necesita un campo discriminante para que al hidratarse podamos saber qué instancia generar en el modelo de objetos

37 of 57

Análisis comparativo de las soluciones

Queremos conocer los productos de un proveedor dado que pesen más de 1 kg

SELECT columnasFROM PRODUCTOS prd� WHERE prd.PESO > 1AND prd.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR

SINGLE TABLE

38 of 57

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 > 1AND con.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR� UNIONSELECT columnas� FROM COMPRADOS com� WHERE com.PESO > 1

AND com.PROVEEDOR_ID = :ID_PROVEEDOR_A_BUSCAR� UNIONSELECT columnas� FROM FABRICADOS fab� WHERE fab.PESO > 1AND 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)

39 of 57

Análisis comparativo de las soluciones

Queremos conocer los productos de un proveedor dado que pesen más de 1 kg

SELECT columnasFROM 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 > 1AND 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

40 of 57

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;

41 of 57

Análisis comparativo: relaciones -one

Supongamos ahora que queremos incorporar al modelo cuál es el producto estrella de cada proveedor.

  • Tanto SINGLE_TABLE como JOINED permiten agregar una FK PRODUCTO_ESTRELLA_ID en la entidad Proveedores.
  • En cambio la estrategia TABLE_PER_CLASS no permite trabajar con FK, como hemos visto anteriormente con la relación ITEM_PRODUCTO.

42 of 57

Análisis comparativo: campo discriminator

  • Tanto en la estrategia TABLE_PER_CLASS como en la JOINED la tabla define la instancia concreta a la que pertenece el objeto a instanciar cuando ocurra el proceso de hidratación
  • En la estrategia SINGLE_TABLE, necesitamos agregar esa información para el framework, de manera de saber la clase a la que pertenece el objeto a instanciar

43 of 57

Análisis comparativo: performance

  • Cada JOIN supone un costo adicional para recuperar información, en ese aspecto la estrategia SINGLE_TABLE es la que escala mejor, seguido por la TABLE_PER_CLASS y por último la JOINED, que requiere al menos un JOIN adicional si tenemos una sola superclase abstracta
  • Otras variables que inciden: definición de índices (clustered/non-clustered), tamaño del registro, si se guardan datos en caché, etc.

44 of 57

Proceso de hidratación

  • Recuperamos la información de la base y construimos el grafo de objetos. Lo que debemos decidir es dónde cortar la búsqueda para no bajar todos los datos.

DB

45 of 57

Proceso de hidratación (cont.)

¿Qué query me conviene para poder resolver esta interfaz de usuario?

46 of 57

Proceso de hidratación (cont.)

¿Qué query me conviene para poder resolver esta interfaz de usuario?

SELECT *FROM PEDIDOS p,� JOIN CLIENTES cON p.CLIENTE_ID = c.ID

WHERE FECHA_PEDIDO BETWEEN :FechaDesde AND :FechaHasta

47 of 57

Proceso de hidratación (cont.)

¿Qué query me conviene para poder resolver esta interfaz de usuario?

SELECT *FROM PEDIDOS p,� JOIN CLIENTES cON p.CLIENTE_ID = c.ID

WHERE FECHA_PEDIDO BETWEEN :FechaDesde AND :FechaHasta

Cliente

Lista de items

1.215.259

Pedido

BASF SA

48 of 57

Proceso de hidratación (cont.)

En cambio, si selecciono una factura para ver el detalle...

49 of 57

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

50 of 57

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)

51 of 57

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.

52 of 57

Ciclo de vida de los objetos

  • ¿Tiene sentido guardar las notas de un alumne que no existe más?
  • ¿Y guardar los datos de un cliente aunque borremos la factura?

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.

53 of 57

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

54 of 57

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.

55 of 57

¿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.

56 of 57

Preguntas

57 of 57