jueves, 21 de abril de 2011

JPA II - Herencia

Este tutorial pretende explicar al lector como mapear la herencia (del modelo de objetos) al modelo relacional.
Al final de este tutorial, el lector encontrara un link para descargar los ejemplos vistos en todos los tutoriales JPA de este blog.

Temas a tratar:
  • Estrategia de asignacion "Single Table"
  • Estrategia de asignacion "Joined"
  • Estrategia de asignacion "Table per concrete class"
  • Clases abstractas (mapped superclass)

Tomamos como ejemplo el siguiente modelo de negocio




1. SINGLE TABLE STRATEGY

Todas las subclases (CheckingAccount y SavingAccount) se mapearan en una sola tabla en la base de datos. Esta tabla tendra una columna llamada discriminador cuyo valor nos permitira mapear la fila (row) con la subclase correspondiente.
Una sola tabla para guardar toda la jerarquía de clases.



Tanto las instancias de la superclase como las subclases seran almancenadas en una sola tabla que tendra el nombre por defecto de la clase raiz (superclase)

El nombre de la columna discriminatoria es por default DTYPE y su tipo son por defecto String. Podemos cambiar estos valores mediante las anotaciones @DiscriminatorColumn y @DiscriminatorValue

Esta estrategia es muy performante a la hora de hacer varias opraciones de lectura / escritura ya que no necesita hacer joins
La desventaja que tiene este tipo de estrategia es el gran desperdicio de almacenamiento de base de  datos especialmente si las subclases contienen un gran numero de propiedades.
 
Esta estrategia soporta queries polimorficas, es decir, queries que en el FROM de la clausula no solo admiten entidades concretas. Ejemplo, para este ejemplo, se podran hacer queries del estilo:
SELECT a FROM Account a
donde se obtendran tanto las cuentas del tipo caja de ahorro (SavinAccounts) como las cuentas corrientes (CheckingAccount).




@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="ACCOUNT_TYPE")
public abstract class Account implements Serializable{
   
    @Id
    @Column(name="ACCOUNT_ID")
    private int id;
   
    private double balance;

    getters y setters...


}


A continuacion, explicamos las anotaciones de la entidad Account:
 
La clase account debera ser abstracta ya que nunca debera ser instanciada. Siempre instanciaremos el tipo de cuenta caja de ahorro (saving account) o el tipo de cuenta cuenta corriente (checking account)
La clase account no tendra constructor 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) denota que la se utilizara la estrategia de single table ( una sola tabla para las super clase como para todas las sub clases ).
@DiscriminatorColumn(name="ACCOUNT_TYPE")  denota que la columa ACCOUNT_TYPE actuara como columna discriminadora. Esto permite sobreescribir los valores por defectos que utilizaria JPA para esto, Es decir, con esto evitamos que JPA utilice el nombre de DTYPE para dicha columna.





@Entity
@DiscriminatorValue("C")
/**
 * Cuenta corriente
 */
public class CheckingAccount extends Account{
   
    private double overdraftLimit;

    public CheckingAccount() {
       
    }

    getters y setters...
}



A continuacion, explicamos las anotaciones de la entidad CheckingAccount:

Esta es una clase concreta y tendra su constructor.
@DiscriminatorValue("C") esta anotacion indicara que en la columna discriminadora, el valor para las instancias de cuentas corrientes tendra el valor "C"



@Entity
@DiscriminatorValue("S")
/**
 * Caja de ahorro
 */
public class SavingAccount extends Account{
   
    private double interestRate;
   
    public SavingAccount() {
       
    }
   
    getters y setters...
}


A continuacion, explicamos las anotaciones de la entidad SavingAccount:


Esta es una clase concreta y tendra su constructor.
@DiscriminatorValue("S") esta anotacion indicara que en la columna discriminadora, el valor para las instancias de cajas de ahorros tendra el valor "S"




2. JOINED STRATEGY

La superclase (Account) y las subclases (CheckingAccount y SavingAccount) se mapearan cada una en una tabla en la base de datos. En cada una de las tablas que representaran a las subclases, las PK actuaran como FK a las PK de la tabla de la superclase (Account).
Una tabla para el padre de la jerarquía, con las cosas comunes, y otra tabla para cada clase hija con las cosas concretas


Aquí podemos notar que en las tablas CHECKINGACCOUNT Y SAVINACCOUNT, las columna, ACCOUNT_ID son una FK que referencian a la columna ACCOUNT_ID de la tabla ACCOUNT.

Esta estrategia es eficiente respecto al almacenamiento de los datos en la BD. Sin embargo para obtener los datos requiere de JOINS.

Al igual que la estrategia anterior, esta también soporta queries polimorficos.

Solo un cambio se debe hacer respecto al ejemplo que se utilizo para la estregia de single table. Se deberá cambiar la anotación @Inheritance de la siguiente manera:


@Inheritance(strategy=InheritanceType.JOINED)





3. TABLE PER CONCRETE CLASS STRATEGY



En esta estrategia es una tabla por clase concreta. Cada entidad sera mapeada a su propia tabla, incluyendo todos los atributos propios y heredados. Con este sistema no hay tablas compartidas, columnas compartidas ni columna discriminatoria.
Una tabla independiente para cada tipo. En este caso cada tabla es independiente, pero los atributos del padre (atributos comunes en los hijos), tienen que estar repetidos en cada tabla.  

Todas las tablas dentro de una misma familia (superclase y subclases) compartan el valor de la clave primaria, como asi tambien los valores comunes. Ademas, en determinadas ocaciones podra tener problemas de rendimiento, ya que al igual que con la union de subclases, la base de datos tendra que realizar operaciones JOIN ante determinadas solicitudes.

Se deberá cambiar la anotación @Inheritance de la siguiente manera:
@Entity@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)




MAPPED SUPERCLASS Y CLASES ABSTRACTAS


Las mapped superclasses son clases que si bien no seran tenidas en cuenta por JPA, pueden ser extendidas para compartir sus propiedades a otras entidades que si maneja JPA

Ejemplo:

@MappedSuperclass

public abstract class MapedSuperClase {
    private String propiedadUno;
    private int propiedadDos;
   
    // Getters y setters
   
}

@Entity
public class SuperClase extends MappedSuperClase { ... }

Esto lo logramos mediante la anotacion @MappedSuperclass

Solo la subclase SuperClase sera manejada por JPA, sin embargo, en el mapeo se incluiran todas las propiedades heredadas de la clase MappedSuperClase

Dicha anotacion aplica tanto a clases abstractas como a clases concretas.

La principal desventaja de las mapped superclass es que no pueden ser persisitidas ni utilizadas en queries.

Por otra parte, las clases abstractas son tratadas de la misma manera que las clases concretas, y que cualquier clase que no sea indicada como entidad con la anotacion @Entity sera ignorada por JPA a la hora de realizar el mapeo objeto-relacional




Descargas de ejemplos de mapeos JPA y consultas JPQL

Por favor, haga clic en: Descargar ejemplos JPA


Bibliografia



* EJB 3 Developer Guide - A Practical Guide for developers and architects to the Enterprise Java Beans
   Standard - Michael Sikora - 2008

8 comentarios:

  1. Antes que nada excelente explicación, me sirvió mucho, hola mi nombre es Antonio, quisiera hacerte una ligera pregunta, @DiscriminatorValue se persiste en las tablas solo con ese mapeado? en la clase padre poner la columna discriminadora y en las hijas el valor, solo con eso ¿cada que persista esa columna se llenara con esto?..

    ResponderEliminar
  2. Hola, tanto para las estrategias de SINGLE TABLE como JOINED STRATEGY se utilizara el concepto de la columna discriminadora y el valor de dicha columna mediante el anotación @DiscriminatorValue. Pata la tercer estrategia no tendrá validez este concepto.

    Exactamente, en la clase padre debes poner la anotación @DiscriminatorColumn mientras que en las clases hijas @DiscriminatorValue (este ultimo es opcional).
    Si elegimos la estrategia de SINGLE TABLE o JOINED, vamos a tener una columna en la tabla que actuara de discriminadora ( en esta caso ACCOUNT_TYPE ) y dichos valores serán los que hayamos configurado mediante la anotación @DiscriminatorValue.

    Saludos.

    ResponderEliminar
  3. Buena explicación. Pero decirme que pasa si en la super clase existe una propiedad que por sus características no permite la duplicidad. ¿Cómo se puede controlarse eso?

    ResponderEliminar
  4. Es buena la explicación, sin embargo me surge una duda en la JOINED STRATEGY supongamos que tenemos estas entidades:Persona, Empleado y Cliente. Empleado y Cliente heredan a Persona. En persona existe la propiedad nombre y deseo que no existan duplicados en la tabla persona y agregue una anotación que restringue la duplicidad.

    Ahora cuando deseo persistir a una persona llamada XXXXX como Empleado esta esto correcto; sin embargo tengo problemas para persistir a esta misma personas como Cliente por la regla de la no duplicidad. ¿Cómo podría resolver esto? ¿Hay alguna anotación especifica al respecto que al identificar que ese dato ya exista, solo persista los datos en la tabla de Clientes?

    ResponderEliminar
    Respuestas
    1. Me he topado con el mismo problema...

      El tema es que en el fondo no dejan de ser las mismas personas segun el modelo de datos q tu planteas (tanto cliente como Empleado), con lo cual una opción es replantearse el modelo, es decir, tienen que ser personas distintas por lo cual no seria correcto que ambos extiendan de persona. Directamente se podría tener dos entidades (dos tablas por separado), una para Cliente y la otra para Empleado.
      Igualmente si encuentro algo mas, te lo escribo.

      Si encuentras la solución no dejes de postearlo aquí por favor.!
      Gracias!

      Eliminar
  5. Podrías mostrar el código de las clases en el segundo caso?
    Saludos

    ResponderEliminar
  6. Buenas,

    Solo tienes que cambiar el tipo de estrategia de herencia a: Inheritance(strategy=InheritanceType.JOINED).Ademas tienes que quitar de las clases la anotación del discriminador.

    Es decir, quedan las mismas clases que el ejemplo del caso uno con las siguientes dos modificaciones:
    1. Modificar la anotación @Inheritance para que use la estrategia JOINED.
    2. Quitar de las clases las anotaciones referentes al discriminador.

    ResponderEliminar
  7. En el caso de "Anónimo14 de agosto de 2012 04:44"

    El caso del cliente que a la vez es empleado y que ambas extienden la clase Persona, cual deberia ser la forma correcta de trabajar estos casos?

    ResponderEliminar