3 de agosto de 2010

Hibernate - Parte 10: Herencia

La herencia es uno de los mecanismos que nos proporciona la orientación a objetos más poderosos, ya que gracias a ella surgen cosas como la sobreescritura, la reutilización y extensibilidad de código, etc.

Ya que le herencia facilita la creación de clases a partir de otras clases ya existentes en algunas ocasiones nuestras clases entidad necesitarán extender de otras clases, entidades o no. Como por ejemplo, podemos tener una clase "Usuario" de las cuales extiendan las clases "Administrador", "Operador" y "SuperUsuario".

Mapear una jerarquía de clases a tablas relacionales puede ser un problema un poco complejo. Afortunadamente Hibernate nos proporciona 3 formas o estrategias de manejar la herencia con nuestras clases entidad. En este tutorial veremos cómo usar estas tres formas de herencia y las implicaciones que tienen el uso de una u otra. Veremos cómo hacer esto tanto con anotaciones como con archivos de mapeo.

Las tres estrategias de herencia que soporta Hibernate son:

  • Una tabla por toda la jerarquía de clases
  • Una tabla por cada subclase (joins)
  • Una tabla por cada clase concreta (uniones)

A continuación veremos cómo funciona cada una de estas estrategias, pero primero veamos el modelo de datos que usaremos para este tutorial.

Crearemos un proyecto en NetBeans para mostrar los ejemplos (menú "File -> New Project... -> Java -> Java Application"). Le damos un nombre y una ubicación al proyecto y nos aseguramos de que las opciones "Create Main Class" y "Set as Main Project" estén habilitadas. Presionamos el botón "Finish" y veremos aparecer en el editor nuestra clase "Main".

Agregamos la biblioteca de "Hibernate", que creamos en el primer tutorial de la serie. Hacemos clic derecho en el nodo "Libraries" del proyecto. En el menú contextual que se abre seleccionamos la opción "Add Library...":



En la ventana que se abre seleccionamos la biblioteca "Hibernate":



Presionamos el botón "Add Library" para que la biblioteca se agregue a nuestro proyecto. Si van a usar anotaciones además deben agregar la biblioteca "HibernateAnotaciones" que creamos en el segundo tutorial. Aprovechamos también para agregar el conector de MySQL. Debemos tener los siguientes archivos en nuestro proyecto:



Ahora creamos dos paquetes, uno con el nombre "modelo", que contendrá las clases entidades, y otro con el nombre "dao" que contendrá nuestras clases para operaciones de persistencia. Adicionalmente si van a trabajar con archivos de mapeo agreguen el paquete "mapeos" que contendrá los archivos de mapeo XML. Hacemos clic derecho en el nodo del paquete que se creó al generar el proyecto. En el menú contextual que se abre seleccionamos la opción "New -> Java Package..." y creamos los paquetes.



Que deben quedar así:



Aprovecharemos para crear nuestro archivo de configuración de Hibernate, "hibernate.cfg.xml", el cual será muy parecido al del primer tutorial:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>

        <!-- parametros para la conexion a la base de datos -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost/hibernateherencia</property>
        <property name="connection.username">usuario</property>
        <property name="connection.password">password</property>

        <!-- Configuracion del pool interno -->
        <property name="connection.pool_size">1</property>

        <!-- Dialecto de la base de datos -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- Otras propiedades importantes -->
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">create-drop</property>

         <!-- Clases o Archivos de mapeo -->
        
    </session-factory>
</hibernate-configuration>


También agregaremos nuestra clase "HibernateUtil" al proyecto. Si quieren usar archivos de mapeo deben usar la que creamos en el primer tutorial; si van a usar anotaciones deben usar la que creamos en el segundo tutorial.

También agregaremos la clase "AbstractDAO", que creamos en el tutorial anterior, en el paquete "dao".

Para los ejercicios usaremos una base de datos llamada "hibernateherencia", en MySQL.

Nota: Yo usaré dos proyectos para cada una de las estrategias de herencia, uno para usar archivos de mapeo y otro para usar anotaciones y que el código de ambos no se mezcle.

Para los ejemplos usaremos un modelo de datos sencillo, que se muestra en la siguiente imagen:



Tenemos una clase base abstracta llamada "Persona", de la cual extienden dos clases: "Tecnologo" y "Normal" (porque por ahí dicen que los tecnólogos no somos normales ;)). Finalmente de la clase "Tecnologo" extienden "Programador" y "Tester".

Como podemos ver en el diagrama, cada una de las clases tiene una serie de atributos propios, lo que nos ayudará a ver cómo se comportan estos en las distintas estrategias de herencia que veremos en un momento.

Las clases quedan de la siguiente manera:

La clase "Persona":

public abstract class Persona implements Serializable
{
    private long id;
    private String nombre;
    private int edad;

    public Persona()
    {
    }

    public Persona(String nombre, int edad)
    {
        this.nombre = nombre;
        this.edad = edad;
    }

    public int getEdad()
    {
        return edad;
    }

    public void setEdad(int edad)
    {
        this.edad = edad;
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


La clase "Normal" queda así:

public class Normal extends Persona
{
    private String ocupacion;
    
    public Normal()
    {
    }

    public Normal(String nombre, int edad, String ocupacion)
    {
        super(nombre, edad);
        this.ocupacion = ocupacion;
    }

    public String getOcupacion()
    {
        return ocupacion;
    }

    public void setOcupacion(String ocupacion)
    {
        this.ocupacion = ocupacion;
    }
}


La clase "Tecnologo" queda de la siguiente forma:

public class Tecnologo extends Persona
{
    private int aniosDeEstudios;
    
    public Tecnologo()
    {
    }

    public Tecnologo(String nombre, int edad, int aniosDeEstudios)
    {
        super(nombre, edad);
        this.aniosDeEstudios = aniosDeEstudios;
    }

    public int getAniosDeEstudios()
    {
        return aniosDeEstudios;
    }

    public void setAniosDeEstudios(int aniosDeEstudios)
    {
        this.aniosDeEstudios = aniosDeEstudios;
    }
}


La clase "Programador" se ve así:

public class Programador extends Tecnologo
{
    private String lenguajeFavorito;
    private int aniosDeExperiencia;

    public Programador()
    {
    }

    public Programador(String nombre, int edad, int aniosDeEstudios, String lenguajeFavorito, int aniosDeExperiencia)
    {
        super(nombre, edad, aniosDeEstudios);
        this.lenguajeFavorito = lenguajeFavorito;
        this.aniosDeExperiencia = aniosDeExperiencia;
    }

    public int getAniosDeExperiencia()
    {
        return aniosDeExperiencia;
    }

    public void setAniosDeExperiencia(int aniosDeExperiencia)
    {
        this.aniosDeExperiencia = aniosDeExperiencia;
    }

    public String getLenguajeFavorito()
    {
        return lenguajeFavorito;
    }

    public void setLenguajeFavorito(String lenguajeFavorito)
    {
        this.lenguajeFavorito = lenguajeFavorito;
    }
}


Finalmente la clase "Tester" queda de la siguiente forma:

public class Tester extends Tecnologo
{
    private String herramientaDeTesteo;
    
    public Tester()
    {
    }

    public Tester(String nombre, int edad, int aniosDeEstudios, String herramientaDeTesteo)
    {
        super(nombre, edad, aniosDeEstudios);
        this.herramientaDeTesteo = herramientaDeTesteo;
    }
    
    public String getHerramientaDeTesteo()
    {
        return herramientaDeTesteo;
    }

    public void setHerramientaDeTesteo(String herramientaDeTesteo)
    {
        this.herramientaDeTesteo = herramientaDeTesteo;
    }
}



Bien, ya que tenemos las clases entidades que estaremos usando, comencemos con el tutorial.


Tabla por Jerarquía de Clases


En esta estrategia se genera una sola tabla en la que se guardan todas las instancias del árbol completo de herencia. O sea, que se genera una sola tabla, y en esta tiene una columna para cada una de las propiedades de cada una de las clases que confirman la jerarquía.

Para saber a qué clase concreta pertenece una fila de esta tabla se usa un valor en una columna discriminadora. Esta columna discriminadora es muy importante ya que gracias a ella sabemos a qué clase pertenece cada una de las filas de nuestra tabla. El valor contenido puede ser cualquiera que nosotros indiquemos para cada una de nuestras entidades, y puede ser una cadena, un carácter, o un entero.

En este caso nuestra base de datos estaría formada por una sola tabla, la cual quedaría de la siguiente forma:



Donde "DISC" es nuestra columna discriminadora, o sea que en esta columna se guardará el valor que nos indicará a cuál de nuestras clases pertenece cada una de las instancias que almacenemos en la base de datos.

Como podemos ver en la imagen anterior, la tabla de nuestra base de datos tiene las propiedades de todas las clases que conforman la jerarquía de "Persona".

Esta estrategia es buena en el sentido de que, como solo tenemos una tabla, no es necesario hacer joins o uniones para recuperar los datos de uno a varios objetos, por lo que es ideal en situaciones en las que necesitamos un rendimiento muy alto, como para la generación de reportes.

Sin embargo no todo siempre es color de rosa, ya que las columnas que representan a los atributos de las subclases deben declararse con la posibilidad de tener valores nulos, ya que como solo guardaremos un objeto de una clase por fila, la columnas que representen a los atributos que no tenga ese objeto quedarán vacías. Debido a esto puede ser que se tengan problemas de integridad.

Ahora veremos cómo implementar esta estrategia de herencia en nuestras aplicaciones.


Tabla por Jerarquía de clases usando Archivos de Mapeo


Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos el nombre de "Persona.hbm" (el asistente se encargará de colocar el ".xml")

Presionamos el botón "Next >" e indicamos que queremos crear un documento XML bien formado (la primera opción) y presionamos el botón "Finish".

Eliminamos el contenido del archivo creado y colocamos el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
    
    </class>
</hibernate-mapping>


El mapeo de la clase "Persona" es muy simple, como el del primer tutorial de la serie, con la única diferencia de que debemos indicarle cuál será el nombre y el tipo de la columna discriminadora de la siguiente forma:

<discriminator column="DISC" type="string" />


En donde indicamos que el nombre de nuestra columna discriminaora será "DISC" (aunque puede tener el nombre que ustedes elijan) y que será de tipo "string". Este elemento debe ir después del elemento "id" en el archivo de mapeo de esa forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>
        
        <discriminator column="DISC" type="string" />
        
    </class>
</hibernate-mapping>


El mapeo completo de "Persona" queda de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>
    
        <discriminator column="DISC" type="string" />
    
        <property name="nombre" />
        <property name="edad" />

    </class>
</hibernate-mapping>


Como podemos ver, es prácticamente igual al que vimos en el primer tutorial. Pasemos ahora a ver cómo comenzar a mapear cada una de sus subclases.

Para mapear una subclase en la estrategia de una sola tabla por jerarquía de clases usamos el elemento "<subclass>" en vez de "<class>" en el archivo de mapeo. Por ejemplo, para mapear la clase "Normal" creamos un nuevo archivo de mapeo llamado "Normal.hbm.xml" en el paquete "mapeos". Eliminamos el contenido del archivo generado y colocamos el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <subclass name="hibernate.herencia.modelo.Normal" >
    
    </subclass>
</hibernate-mapping>


Como vemos, ahora en vez de colocar el elemento "<class>" colocamos "<subclass>". En este elemento indicamos, como se muestra arriba, el nombre de la clase que estamos mapeando (que en este caso es nuestra clase "Normal"); pero además debemos indicar otras cosas. ¿Recuerdan que en el mapeo de la clase base indicamos una columna discriminadora? ¿Y recuerdan que cuando lo colocamos dijimos que cada subclase debe indicar su valor tener un valor para esa columna discriminadora? Pues bien, el elemento "<subclass>" es el lugar para indicar este valor. Para el caso de la clase "Normal" indicamos que su valor para la columna discriminadora será "nrm" usando el atributo "discriminator-value":

<subclass name="hibernate.herencia.modelo.Normal" discriminator-value="nrm">


Finalmente, indicamos de cuál de nuestras clases extiende, con el atributo "extends":

<subclass name="hibernate.herencia.modelo.Normal" discriminator-value="nrm" hibernate.herencia.modelo.Persona">


Y listo, esto es todo lo que necesitamos hacer para indicar en nuestro mapeo que usaremos la estrategia de una sola tabla.

El resto del archivo de mapeo de la clase "Normal" es igual a los que ya hemos visto, y queda de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <subclass name="hibernate.herencia.modelo.Normal" discriminator-value="nrm" extends="hibernate.herencia.modelo.Persona">
        <property name="ocupacion" />
    </subclass>
</hibernate-mapping>


Como podemos ver, no es muy diferente a los mapeos que hemos hecho hasta ahora, solo hay que recordar cambiar el elemento "<class>" por "<subclass>" y agregar algunos atributos.

Fijense que en la clase "Normal" no fué necesario colocar un "id", ya que este será heredado de su clase padre "Persona".

El resto de los mapeos son prácticamente iguales, solo hay que tener cuidado en cambiar el valor del discriminador en cada una de nuestras subclases, viendo que no haya dos iguales, y qué clase extiende cuál subclase. Para el resto de los mapeos los discriminadores quedarán de esta forma:

ClaseDisc
Normalnrm
Tecnologotec
Programadorpro
Testertes


Y los archivos de mapeo quedan de la siguiente forma:

Para la clase "Tecnlologo":

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <subclass name="hibernate.herencia.modelo.Tecnologo" discriminator-value="tec" extends="hibernate.herencia.modelo.Persona">
        <property name="aniosDeEstudios" />
    </subclass>
</hibernate-mapping>


Para la clase "Programador":

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <subclass name="hibernate.herencia.modelo.Programador" discriminator-value="pro" extends="hibernate.herencia.modelo.Tecnologo">
        <property name="lenguajeFavorito" />
        <property name="aniosDeExperiencia" />
    </subclass>
</hibernate-mapping>


Y para la clase "Tester":

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <subclass name="hibernate.herencia.modelo.Tester" discriminator-value="tes" extends="hibernate.herencia.modelo.Tecnologo">
        <property name="herramientaDeTesteo" />
    </subclass>
</hibernate-mapping>


Como vemos, esta forma de indicar la herencia es realmente simple. No olviden indicar todos estos archivos de mapeo en el archivo "hibernate.cfg.xml":

<mapping resource="hibernate/herencia/mapeos/Persona.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Normal.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tecnologo.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Programador.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tester.hbm.xml" />


Adicionalmente también es posible indicar toda la jerarquía en un solo archivo de mapeo de esta forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>

        <discriminator column="DISC" type="string" />
    
        <property name="nombre" />
        <property name="edad" />

        <subclass name="hibernate.herencia.modelo.Normal" discriminator-value="nrm">
            <property name="ocupacion" />
        </subclass>

        <subclass name="hibernate.herencia.modelo.Tecnologo" discriminator-value="tec">
            <property name="aniosDeEstudios" />
        </subclass>

        <subclass name="hibernate.herencia.modelo.Programador" discriminator-value="pro">
            <property name="lenguajeFavorito" />
            <property name="aniosDeExperiencia" />
        </subclass>

        <subclass name="hibernate.herencia.modelo.Tester" discriminator-value="tes">
            <property name="herramientaDeTesteo" />
        </subclass>

    </class>
</hibernate-mapping>


En el archivo de mapeo anterior tenemos los elementos "<subclass>", que representan cada una de las subclases que extienden de "Persona", dentro del elemento "<class>" que mapea a esta clase. Además aquí no es necesario indicar de cuál clase extiende cada una de las subclases, ya que esta información se infiere del elemento "<class>" que las contiene.

Esta forma de mapeo me parece ideal si nuestras subclases no tendrán muchos atributos, así no es necesario crear un archivo de mapeo para cada una de ellas.

Probemos cómo es generada nuestra tabla y como se guardan los mismos en nuestra base de datos. En el método "main" de nuestra clase "Main" guardemos un elemento de cada uno de los tipos de objetos, y adicionalmente guardemos otro "Programador":


public static void main(String[] args)
{
    Normal      normal       = new Normal("normal", 21, "Empleado");
    Tecnologo   tecnologo    = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester      tester       = new Tester("tester", 18, 3, "JUnit");
    
    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


En el código anterior usamos nuestra clase "AbstractDAO" para almacenar los 5 objetos que creamos. Ahora ejecutemos la aplicación y veamos la tabla generada y los datos que se almacenaron en ella.

Vemos qué tablas están en nuestra base de datos "hibernateherencia" y que, efectivamente, solo tenemos una tabla llamada "personas". Si vemos cómo está conformada esta tabla nos encontramos con que tiene una columna para cada uno de los atributos de cada una de las clases dentro de la jerarquía que hemos usado para el ejemplo:



Ahora veamos los datos que contiene:



Como vemos, se han guardado los 5 objetos en esta tabla, justamente como lo estábamos esperando. En la imagen podemos ver los valores que se han guardado en la columna discriminadora y que los valores de las columnas que no tiene una clase se ha establecido a "NULL" en todos los casos.

Ahora veamos cómo hacer esto mismo pero usando archivos de mapeo.


Tabla por Jerarquía de Clases usando Anotaciones


Nuestras clases entidad serán anotadas prácticamente igual que en el segundo tutorial, con unas pequeñas diferencias para indicar el tipo de herencia que queremos usar.

Para indicar que nuestras entidades harán uso de herencia en nuestra base de datos usamos la anotación "@Inheritance" en la clase que representa la raíz de la jerarquía, en este caso en la clase "Persona". Para indicar cuál de las estrategias de herencia queremos usar usamos su atributo "strategy" de esta forma:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class Persona implements Serializable
{
}


Sin embargo cómo el valor por default del atributo "strategy" es "InheritanceType.SINGLE_TABLE" podríamos no poner nada dejándolo así:

@Entity
@Inheritance
public abstract class Persona implements Serializable
{
}


Solo con eso ya estamos indicando que la clase "Persona" es una entidad y que ella y todas las clases que extiendan de ella usarán la estrategia de herencia de una sola tabla. Sin embargo aún podemos indicar algo más. ¿Recuerdan la columna discriminadora que se usa en la tabla? Pues también podemos indicar cuál será el nombre y el tipo de esta columna. Si no indicamos nada (como arriba) el nombre de la columna será "DTYPE" y el tipo será "String". Si queremos indicar alguna otra cosa podemos hacerlo mediante la anotación "@DiscriminatorColumn". Esta anotación tiene dos atributos importantes: "name" que indica cuál será el nombre de la columna, y "discriminatorType" que indica de qué tipo será el discriminador. Este último atributo puede tomar uno de tres valores:

  • DiscriminatorType.STRING (este es el valor por default)
  • DiscriminatorType.CHAR
  • DiscriminatorType.INTEGER

Cambiemos el nombre que Hibernate le da por default a nuestra columna discriminadora para usar "DIS":

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DIS", discriminatorType=DiscriminatorType.STRING)
public abstract class Persona implements Serializable
{
}


Y el resto de las anotaciones que usamos en "Persona" son las que ya habíamos visto en el segundo tutorial. Al final la clase "Persona" queda así:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DIS", discriminatorType=DiscriminatorType.STRING)
public abstract class Persona implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    
    private String nombre;
    private int edad;

    public Persona()
    {
    }

    public Persona(String nombre, int edad)
    {
        this.nombre = nombre;
        this.edad = edad;
    }

    public int getEdad()
    {
        return edad;
    }

    public void setEdad(int edad)
    {
        this.edad = edad;
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


Ahora veremos cómo se hace el mapeo del resto de nuestras clases que, en realidad, es algo bastante simple.

Para hacer que nuestra clase "Normal" quede lista para ser usada en nuestra aplicación solo es necesario hacer dos cosas. La primera es indicar, usando la anotación "@Entity" que se trata de una entidad, de esta forma:

@Entity
public class Normal extends Persona
{
}


Hasta aquí todo sigue siendo lo que ya conocemos. El cambio viene solamente en que hay que indicar, al igual que lo hicimos con los archivos de mapeo, cuál será el valor que identificará a los registros de esta entidad en la columna discriminadora. Y esto lo hacemos con la anotación "@DiscriminatorValue" en cuyo atributo "value" indicamos cuál será el valor que tendrá nuestra columna. En el caso de "Normal" pondremos que el valor para la columna discriminadora que lo representará será "NM":

@Entity
@DiscriminatorValue(value="NM")
public class Normal extends Persona
{
}


Y listo. El resto de la clase, en este caso, no lleva ninguna anotación, y al final queda de la siguiente manera:

@Entity
@DiscriminatorValue(value="NM")
public class Normal extends Persona
{
    private String ocupacion;
    
    public Normal()
    {
    }

    public Normal(String nombre, int edad, String ocupacion)
    {
        super(nombre, edad);
        this.ocupacion = ocupacion;
    }

    public String getOcupacion()
    {
        return ocupacion;
    }

    public void setOcupacion(String ocupacion)
    {
        this.ocupacion = ocupacion;
    }
}


Para el resto de las clases hacemos exactamente lo mismo, indicamos con "@Entity" que nuestra clase será una entidad, y usamos "@DiscriminatorValue" para indicar cuál será el valor de la clase en la columna discriminadora. Para el resto de las clases los discriminadores quedarán de esta forma:

ClaseDisc
NormalNM
TecnologoTC
ProgramadorPG
TesterTS


Y las clases de la siguiente forma:

La clase "Tecnologo" queda así:

@Entity
@DiscriminatorValue(value="TC")
public class Tecnologo extends Persona
{
    private int aniosDeEstudios;

    public Tecnologo()
    {
    }

    public Tecnologo(String nombre, int edad, int aniosDeEstudios)
    {
        super(nombre, edad);
        this.aniosDeEstudios = aniosDeEstudios;
    }

    public int getAniosDeEstudios()
    {
        return aniosDeEstudios;
    }

    public void setAniosDeEstudios(int aniosDeEstudios)
    {
        this.aniosDeEstudios = aniosDeEstudios;
    }
}


La clase "Programador" queda así:

@Entity
@DiscriminatorValue(value="PG")
public class Programador extends Tecnologo
{
    private String lenguajeFavorito;
    private int aniosDeExperiencia;

    public Programador()
    {
    }

    public Programador(String nombre, int edad, int aniosDeEstudios, String lenguajeFavorito, int aniosDeExperiencia)
    {
        super(nombre, edad, aniosDeEstudios);
        this.lenguajeFavorito = lenguajeFavorito;
        this.aniosDeExperiencia = aniosDeExperiencia;
    }

    public int getAniosDeExperiencia()
    {
        return aniosDeExperiencia;
    }

    public void setAniosDeExperiencia(int aniosDeExperiencia)
    {
        this.aniosDeExperiencia = aniosDeExperiencia;
    }

    public String getLenguajeFavorito()
    {
        return lenguajeFavorito;
    }

    public void setLenguajeFavorito(String lenguajeFavorito)
    {
        this.lenguajeFavorito = lenguajeFavorito;
    }
}


Y Finalmente la clase "Tester" queda de esta forma:

@Entity
@DiscriminatorValue(value="TS")
public class Tester extends Tecnologo
{
    private String herramientaDeTesteo;

    public Tester()
    {
    }

    public Tester(String nombre, int edad, int aniosDeEstudios, String herramientaDeTesteo)
    {
        super(nombre, edad, aniosDeEstudios);
        this.herramientaDeTesteo = herramientaDeTesteo;
    }

    public String getHerramientaDeTesteo()
    {
        return herramientaDeTesteo;
    }

    public void setHerramientaDeTesteo(String herramientaDeTesteo)
    {
        this.herramientaDeTesteo = herramientaDeTesteo;
    }
}


No olviden agregar estas clases al archivo de configuración "hibernate.cfg.xml" de la siguiente forma:

<mapping class="hibernate.herencia.modelo.Persona"/>
<mapping class="hibernate.herencia.modelo.Normal"/>
<mapping class="hibernate.herencia.modelo.Tecnologo"/>
<mapping class="hibernate.herencia.modelo.Programador"/>
<mapping class="hibernate.herencia.modelo.Tester"/>


Probemos que lo anterior funciona usando, nuevamente, el siguiente código en el método "main" de nuestra clase "Main".

public static void main(String[] args)
{
    Normal normal = new Normal("normal", 21, "Empleado");
    Tecnologo tecnologo = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester tester = new Tester("tester", 18, 3, "JUnit");

    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


Al ejecutar la aplicación podemos ver que se genera una sola tabla en la base de datos llamada "persona" constituida por las siguientes columnas:



Si vemos qué datos contiene esta tabla podemos observar que se han guardado los datos de la forma que esperábamos y que, al igual que con los archivos de mapeo, en la columna discriminadora se han guardado los valores indicados y que en las columnas que representan los atributos de una clase distinta a la del registro se ha colocado el valor "NULL" en todos los casos:



Como podemos ver, con esta estrategia pasamos de nuestra jerarquía de clases a una sola tabla, como se ilustra en la siguiente imagen:



Ahora que ya hemos visto cómo trabajar con la estrategia de una sola tabla, veamos la siguiente de las estrategias de herencia proporcionadas por hibérnate.


Una Tabla para cada Subclase (joins)


En esta estrategia de herencia se creará una tabla por cada una de las clases que conformen nuestro árbol de herencia. Cada una de las clases y subclases que declaren atributos persistentes, incluyendo clases abstractas e interfaces, tendrá su propia tabla.

La representación de la relación de herencia entra estas tablas se hace mediante una llave foránea.

A diferencia de la estrategia de una tabla por jerarquía de clases, aquí cada tabla contiene solamente las columnas que representan los atributos declarados en la clase, junto con una columna para la llave primaria que es también una llave foránea de la super clase. Por lo tanto nuestra base de datos quedará de la siguiente forma:



Las tablas que representan a las clases padre y las que representan a las clases hijas son unidas por el valor de sus llaves primarias compartidas. Gracias a esto es posible recuperar de la base de datos las instancias de una subclase haciendo un "join" entre la tabla de la subclase con la de la superclase.

Esta estrategia tiene la ventaja de que las modificaciones y actualizaciones de una clase no afectan a los datos almacenados de las otras clase, además de que nuestro esquema de base de datos está normalizado.

Su desventaja es que la búsqueda de cuál clase concreta pertenece una fila debe realizarse en cada una de las tablas de las clases que componen la jerarquía, que en este caso serían 5. Debido a esto mismo, si debemos escribir nosotros mismos algo de SQL (digamos, para hacer reportes) la tarea se complica.

Además de esto el rendimiento puede ser muy malo si tenemos jerarquías complejas de clases.

Veamos cómo implementar esta estrategia en nuestra aplicación.


Una Tabla para cada Subclase (joins) usando Archivos de Mapeo


Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos el nombre de "Persona.hbm" (el asistente se encargará de colocar el ".xml")

Presionamos el botón "Next >" e indicamos que queremos crear un documento XML bien formado (la primera opción) y presionamos el botón "Finish".

En esta estrategia la clase base no necesita tener algo especial. Por lo que el mapeo de clase "Persona" queda de forma simple. Eliminamos el contenido del archivo creado y colocamos el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>

        <property name="nombre" />
        <property name="edad" />

    </class>
</hibernate-mapping>


Ahora comienza la parte interesante que es crear el mapeo de las subclases. Para indicar que usaremos la estrategia de una tabla por subclase usamos, en el archivo de mapeo de las subclases, en vez del elemento "<class>", el elemento "<joined-subclass>". En este elemento indicamos el nombre de la clase que estamos mapeando, usando el atributo "name", junto con la tabla en la que se guardarán, usando el atributo "table", de la siguiente forma:

<hibernate-mapping>
    <joined-subclass name="hibernate.herencia.modelo.Normal" table="normales">

    </joined-subclass>
</hibernate-mapping>


También, dentro del elemento "<joined-subclass>" indicamos de cuál clase extiende la clase que estamos mapeando, de la siguiente forma:

<joined-subclass name="hibernate.herencia.modelo.Normal" table="normales" extends="hibernate.herencia.modelo.Persona">


Ahora debemos indicar cuál de las columnas de la tabla que almacenará los datos de la entidad padre es la que almacena su identificador, ya que está columna será usada como llave foránea/primaria de la tabla que se generará para almacenar los datos de la entidad que estamos mapeando. Para eso hacemos uso del elemento "<key>", y en su atributo "column" indicamos el nombre de la columna que almacena el identificador de su entidad padre, de la siguiente forma:

<key column="id"  />


En este caso la columna que se generará para almacenar el identificador de "Persona" es la mismo nombre que el de la propiedad ("id") ya que no hemos indicado ninguna otra cosa, pero si, por ejemplo, este se estuviera guardando en una columna llamada "ID_PERSONA" tendríamos que colocar "ID_PERSONA" en el atributo "column" del elemento "<key>".

Esto es lo único que tenemos que hacer para indicar que queremos usar esta estrategia de herencia. El resto del archivo es el mapeo normal de la clase, y al final queda de esta forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <joined-subclass name="hibernate.herencia.modelo.Normal" table="normales" extends="hibernate.herencia.modelo.Persona">
            <key column="id"  />
        <property name="ocupacion" />
    </joined-subclass>
</hibernate-mapping>


El resto de los mapeos se hace exactamente igual, quedando al final de la siguiente forma:

Para la clase "Tecnologo" el mapeo queda de esta forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <joined-subclass name="hibernate.herencia.modelo.Tecnologo" table="tecnologos" extends="hibernate.herencia.modelo.Persona">

        <key column="id"  />

        <property name="aniosDeEstudios" />
    </joined-subclass>
</hibernate-mapping>


El mapeo para la clase "Programador" queda así:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <joined-subclass name="hibernate.herencia.modelo.Programador" table="programadores" extends="hibernate.herencia.modelo.Tecnologo">

        <key column="id"  />

        <property name="lenguajeFavorito" />
        <property name="aniosDeExperiencia" />
    </joined-subclass>
</hibernate-mapping>


Finalmente, el mapeo de "Tester" queda de esta forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <joined-subclass name="hibernate.herencia.modelo.Tester" table="testers" extends="hibernate.herencia.modelo.Tecnologo">

        <key column="id"  />

        <property name="herramientaDeTesteo" />
    </joined-subclass>
</hibernate-mapping>


No olviden poner estos archivos de mapeo en el archivo "hibernate.cfg.xml":

<mapping resource="hibernate/herencia/mapeos/Persona.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Normal.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tecnologo.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Programador.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tester.hbm.xml" />


Igual que en la estrategia anterior, aquí podemos colocar todos los mapeos en un mismo archivo y así ya no es necesario indicar de cuál clase extienden las clases que estamos mapeando:

<?xml version="1.0"?>   
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>

        <property name="nombre" />
        <property name="edad" />

        <joined-subclass name="hibernate.herencia.modelo.Normal" table="normales">
            <key column="id"  />
            <property name="ocupacion" />
        </joined-subclass>
        
        <joined-subclass name="hibernate.herencia.modelo.Tecnologo" table="tecnologos">
            <key column="id"  />
            <property name="aniosDeEstudios" />
        </joined-subclass>
        
        <joined-subclass name="hibernate.herencia.modelo.Programador" table="programadores">
            <key column="id"  />
            <property name="lenguajeFavorito" />
            <property name="aniosDeExperiencia" />
        </joined-subclass>
        
        <joined-subclass name="hibernate.herencia.modelo.Tester" table="testers">
            <key column="id"  />
            <property name="herramientaDeTesteo" />
        </joined-subclass>

    </class>
</hibernate-mapping>


Probemos que nuestra aplicación funciona correctamente usando el código que teníamos anteriormente en el método "main" de nuestra clase "Main":

public static void main(String[] args) 
{
    Normal normal = new Normal("normal", 21, "Empleado");
    Tecnologo tecnologo = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester tester = new Tester("tester", 18, 3, "JUnit");

    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


Comprobemos qué, en nuestra base de datos, efectivamente se haya creada una sola tabla para cada una de nuestras clases:



Ahora comprobemos los datos que contiene cada una de las tablas:



Como se ve en la imagen anterior, los datos comunes de todas las clases se guardan en la tabla "personas" y los datos particulares de cada una de las subclases se almacena en su tabla correspondiente.

Veamos cómo hacer esto mismo con anotaciones.


Una Tabla para cada Subclase (joins) usando Anotaciones


Las anotaciones para esta estrategia de herencia también son muy parecidas a las que ya estamos acostumbrados a usar.

En nuestra clase base, la clase "Persona", usamos la anotación "@Inheritance". Para indicar que queremos usar esta estrategia, lo indicamos en el atributo "strategy" de esa anotación con el valor "InheritanceType.JOINED", de la siguiente forma:

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Persona implements Serializable
{
}


Y listo, es todo lo que demos hacer en nuestra clase base. El resto de las anotaciones de la clase "Persona" son las que ya conocemos, al final queda así:

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Persona implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String nombre;
    private int edad;

    public Persona()
    {
    }

    public Persona(String nombre, int edad)
    {
        this.nombre = nombre;
        this.edad = edad;
    }

    public int getEdad()
    {
        return edad;
    }

    public void setEdad(int edad)
    {
        this.edad = edad;
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


En las subclases no es necesario que hagamos algo especial en esta estrategia de herencia. Por ejemplo, para la clase "Normal" basta con que la indiquemos de la siguiente forma:

@Entity
public class Normal extends Persona
{
}


Simplemente con indicar que "Normal" es una entidad y que extiende de "Persona" es suficiente en esta estrategia de herencia. El resto de la clase queda de la siguiente forma:

@Entity
public class Normal extends Persona
{
    private String ocupacion;

    public Normal()
    {
    }

    public Normal(String nombre, int edad, String ocupacion)
    {
        super(nombre, edad);
        this.ocupacion = ocupacion;
    }

    public String getOcupacion()
    {
        return ocupacion;
    }

    public void setOcupacion(String ocupacion)
    {
        this.ocupacion = ocupacion;
    }
}


El resto de las clases queda exactamente igual, así que no las colocaré aquí. Pero no olviden ponerlas en el archivo "hibernate.cfg.xml" de la siguiente forma:

<mapping class="hibernate.herencia.modelo.Persona"/>
<mapping class="hibernate.herencia.modelo.Normal"/>
<mapping class="hibernate.herencia.modelo.Tecnologo"/>
<mapping class="hibernate.herencia.modelo.Programador"/>
<mapping class="hibernate.herencia.modelo.Tester"/>


Probemos que todo funciona correctamente con el código que hemos estado usando en nuestro método "main":

public static void main(String[] args) 
{
    Normal normal = new Normal("normal", 21, "Empleado");
    Tecnologo tecnologo = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester tester = new Tester("tester", 18, 3, "JUnit");

    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


Veamos las tablas generadas en nuestra base de datos:



Y los datos que contiene cada una de ellas:



Comprobamos nuevamente que todo salió como lo planeamos ^_^.

Ahora veamos un poco el SQL generado con esta estrategia. Para esto modifiquemos un poco nuestra aplicación para recuperar el primer "Programador" que guardamos. Agreguemos esta línea al final del método "main":

AbstractDAO.getEntidad(programador1.getId(), Programador.class);


No nos interesa trabajar con la entidad recuperada, solo ver el sql generado por Hibernate para recuperarla:



Si vemos el SQL que resalté en la imagen anterior, veremos que tenemos lo siguiente:

select programado0_.id as id0_0_, programado0_2_.nombre as nombre0_0_, programado0_2_.edad as edad0_0_, programado0_1_.aniosDeEstudios as aniosDeE2_2_0_, programado0_.lenguajeFavorito as lenguaje2_3_0_, programado0_.aniosDeExperiencia as aniosDeE3_3_0_ from programadores programado0_ inner join tecnologos programado0_1_ on programado0_.id=programado0_1_.id inner join personas programado0_2_ on programado0_.id=programado0_2_.id where programado0_.id=?


Lo modificaré un poco para hacerlo más claro:

SELECT 
  prog.id as id0_0_, pers.nombre as nombre0_0_, pers.edad as edad0_0_, tec.aniosDeEstudios as aniosDeE2_2_0_, prog.lenguajeFavorito as lenguaje2_3_0_, prog.aniosDeExperiencia as aniosDeE3_3_0_ 

FROM 
  programadores prog 
  
     INNER JOIN 
     
  tecnologos tec on prog.id=tec.id 
  
     INNER JOIN 
  
  personas pers on prog.id=pers.id 

WHERE prog.id=?


Podemos observar que, efectivamente, se crea un join para unir cada una de las tres tablas que forman la jerarquía de "Programador", que en este caso son "programadores", "tecnologos", y "personas". Sin embargo en este caso nosotros indicamos a cuál clase pertenece la instancia que queremos recuperar (con "Programador.class"). ¿Pero qué ocurriría si no sabemos exactamente a cuál de las subclases de "Persona" pertenece la entidad? En ese caso nuestro código tendría que ser así:

AbstractDAO.getEntidad(programador1.getId(), Persona.class);


Ahora no estamos seguros de a cuál clase pertenece el objeto que queremos recuperar (bueno, nosotros sabemos que buscamos un "Pogramador", pero Hibernate solo sabe que busca una sublcase de "Persona"). Por lo tanto, si ejecutamos la aplicación de esta forma, Hibernate genera la siguiente consulta para saber cuál clase recuperar:

select persona0_.id as id0_0_, persona0_.nombre as nombre0_0_, persona0_.edad as edad0_0_, persona0_1_.ocupacion as ocupacion1_0_, persona0_2_.aniosDeEstudios as aniosDeE2_2_0_, persona0_3_.lenguajeFavorito as lenguaje2_3_0_, persona0_3_.aniosDeExperiencia as aniosDeE3_3_0_, persona0_4_.herramientaDeTesteo as herramie2_4_0_, case when persona0_3_.id is not null then 3 when persona0_4_.id is not null then 4 when persona0_1_.id is not null then 1 when persona0_2_.id is not null then 2 when persona0_.id is not null then 0 end as clazz_0_ from personas persona0_ left outer join normales persona0_1_ on persona0_.id=persona0_1_.id left outer join tecnologos persona0_2_ on persona0_.id=persona0_2_.id left outer join programadores persona0_3_ on persona0_.id=persona0_3_.id left outer join testers persona0_4_ on persona0_.id=persona0_4_.id where persona0_.id=?


Nuevamente lo acomodaré para hacerlo un poco más claro:

SELECT
  pers.id as id0_0_, pers.nombre as nombre0_0_, pers.edad as edad0_0_, norm.ocupacion as ocupacion1_0_, tecs.aniosDeEstudios as aniosDeE2_2_0_, prog.lenguajeFavorito as lenguaje2_3_0_, prog.aniosDeExperiencia as aniosDeE3_3_0_, tetr.herramientaDeTesteo as herramie2_4_0_, 
  
  CASE 
    WHEN prog.id IS NOT NULL THEN 3 
    WHEN tetr.id IS NOT NULL THEN 4 
    WHEN norm.id IS NOT NULL THEN 1 
    WHEN tecs.id IS NOT NULL THEN 2 
    WHEN pers.id IS NOT NULL THEN 0 
  END as clazz_0_ 
  
FROM personas pers 

  LEFT OUTER JOIN 
  
normales norm on pers.id=norm.id 

  LEFT OUTER JOIN 
  
tecnologos tecs on pers.id=tecs.id 

  LEFT OUTER JOIN 
  
programadores prog on pers.id=prog.id 

  LEFT OUTER JOIN 

testers tetr on pers.id=tetr.id 

  WHERE pers.id=?


Ahora la conuslta consulta es "compleja", y entre más entidades tengamos más compleja se volverá. Es por eso que esta estrategia es muy lenta si tenemos consulta en los que no sabemos el tipo concreto de la clase que buscamos, solo la clase base. A estas consultas se les conoce como "Polimórficas".>

Entonces, tenemos que con esta estrategia pasamos de tener un conjunto de clases a tener una tabla por cada una de las clases, como lo ilustra la siguiente figura:



En esta estrategia no fue necesario usar un discriminador, aunque en caso de que lo quisiéramos agregar también es posible (aunque esto no agiliza las consultas polimórficas). Con esto logramos una mezcla entre las dos estrategias anteriores.



Para hacer eso con archivos de mapeo debemos colocar el elemento "<discriminator>" en el archivo de mapeo de la clase base (indicando el nombre y tipo de la columna discriminadora) y, en los archivos de mapeo de las subclases, debemos usar nuevamente el elemento "<subclass>" indicando, mediante su atributo "discriminator-value" el valor que tendrá la columna discriminadora para cada una de las subclases. Como hijo del elemento "<subclass>" debemos colocar el elemento "<join>" indicando el nombre de la tabla que se usará para almacenar los atributos de los objetos de ese tipo, y como hijo de este el elemento "<key>" indicando la columna de la tabla padre que almacena su identificador. El archivo de mapeo (colocando los mapeos de todo el árbol de herencia de "Persona" en un solo archivo) quedaría de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" table="personas">
        <id name="id">
            <generator class="identity" />
        </id>

        <discriminator column="DISC" type="string" />

        <property name="nombre" />
        <property name="edad" />

        <subclass name="hibernate.herencia.modelo.Normal" discriminator-value="nrm">
            <join table="normales">
                <key column="id"  />
                <property name="ocupacion" />
            </join>
        </subclass>

        <subclass name="hibernate.herencia.modelo.Tecnologo" discriminator-value="tcn">
            <join table="tecnologos">
                <key column="id" />
                <property name="aniosDeEstudios" />
            </join>
        </subclass>

        <subclass name="hibernate.herencia.modelo.Programador" discriminator-value="prm">
            <join table="programadores" >
                <key column="id"  />
                <property name="lenguajeFavorito" />
                <property name="aniosDeExperiencia" />
            </join>
        </subclass>

        <subclass name="hibernate.herencia.modelo.Tester" discriminator-value="tst">
            <join table="testers">
                <key column="id"  />
                <property name="herramientaDeTesteo" />
            </join>
        </subclass>

    </class>
</hibernate-mapping>


Con anotaciones parece que no es posible lograr esto. Pero como dije anteriormente: esto no ayuda en realidad a optimizar las consultas polimórficas.

Pasemos a ver la última de las estrategias de herencia que nos proporciona Hibernate.


Una Tabla por cada Clase Concreta (uniones)


Esta última estrategia podría parecer la más extraña ya que en este caso se generará una tabla por cada una de las entidades no-abstractas que tenga nuestra aplicación (en este caso NO se generará una tabla para "Persona"). Sin embargo cada tabla tendrá una columna para cada uno de los atributos de la clase de la entidad que almacena, propios y heredados. O sea, que la tabla mantendrá los atributos de la clase que mapea, junto con los atributos que hereda de su clase padre.

En este caso las tablas no están relacionadas de ninguna forma, por lo que terminaremos con un conjunto de tablas independientes una de otras.

El esquema de la base de datos en esta ocasión quedará de la siguiente forma:



Como vemos, ahora no hay ninguna tabla para "Persona", sin embargo las tablas de "normal" y "tecnologo" tienen las columnas "edad" y "nombre", que son atributos de "Persona", además de las columnas para sus propios atributos. De la misma forma "programador" y "tester" tienen columnas para los atributos de "tecnólogo", además de columnas para sus atributos propios. Además, estas tablas no tienen relación una con otra de ninguna forma.

Esta estrategia tiene la ventaja de que no es necesario hacer un grupo de joins para obtener todos los datos de una entidad, lo que nuevamente nos funciona si haremos consultas SQL a mano.

Ahora veamos cómo usar esta estrategia en nuestras aplicaciones.


Una Tabla por Clase Concreta usando Archivos de Mapeo


Creamos, en el paquete "mapeos" un nuevo documento XML. Le damos el nombre de "Persona.hbm" (el asistente se encargará de colocar el ".xml")

Presionamos el botón "Next >" e indicamos que queremos crear un documento XML bien formado (la primera opción) y presionamos el botón "Finish".

En esta estrategia la clase base no necesita cambiar mucho. Por lo que el mapeo de clase "Persona" queda de forma simple. Eliminamos el contenido del archivo creado y colocamos el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona">

    </class>
</hibernate-mapping>


En este caso en el mapeo de la clase "Persona" (la cual recordemos que es "abstracta) debemos indicar de forma explícita que estamos mapeando una clase abstracta, con el atributo ""abstract" del elemento ""<class>", de la siguiente forma:

<class name="hibernate.herencia.modelo.Persona" abstract="true">


También si se han dado cuenta, en esta ocasión NO usamos el atributo "table" en este elemento. Estas dos cosas son para que no se genere una tabla para las tablas abstractas ya que, como podrán imaginar, estas tablas solo estarían ocupando espacio en la base de datos ya que nunca se usarían.



El siguiente paso es mapear el atributo que se usará como identificador de todas las subclases que extiendan de esta clase en la base de datos. Solo que ahora no podemos usar el generador "identity" como lo hemos estado haciendo hasta ahora. En realidad no entiendo el por qué de esta restricción, pero por el momento tendremos que ajustarnos a esta regla. De hecho los valores que podemos colocar son los siguientes:

  • increment
  • hilo
  • uuid
  • guid
  • assigned

Yo usaré "increment" ya que es el que más se parece a "identity". Si quieren encontrar más información sobre los generadores pueden hacerlo en esta página.

El resto del mapeo de "Persona" no tiene nada de especial, y al final queda de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" abstract="true">
        <id name="id">
            <generator class="assigned" />
        </id>

        <property name="nombre" />
        <property name="edad" />

    </class>
</hibernate-mapping>


En el mapeo de las subclases, en esta ocasión en vez del elemento "<class>" usaremos "<union-subclass>". Indicamos en cuál tabla se almacenan los datos de esta subclase usando el atributo "table", y de cuál clase extiende usando el atributo "extends". Por ejemplo en la clase "Normal" el mapeo quedaría de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <union-subclass name="hibernate.herencia.modelo.Normal" table="normales" extends="hibernate.herencia.modelo.Persona">
        <property name="ocupacion" />
    </union-subclass>
</hibernate-mapping>


El de "Tecnologo" quedaría de esta forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <union-subclass name="hibernate.herencia.modelo.Tecnologo" table="tecnologos" extends="hibernate.herencia.modelo.Persona">
        <property name="aniosDeEstudios" />
    </union-subclass>
</hibernate-mapping>


El mapeo de "Programador" queda así:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <union-subclass name="hibernate.herencia.modelo.Programador" table="programadores" extends="hibernate.herencia.modelo.Tecnologo">
        <property name="lenguajeFavorito" />
        <property name="aniosDeExperiencia" />
    </union-subclass>
</hibernate-mapping>


Y el de la clase "Tester" queda de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <union-subclass name="hibernate.herencia.modelo.Tester" table="testers" extends="hibernate.herencia.modelo.Tecnologo">
        <property name="herramientaDeTesteo" />
    </union-subclass>
</hibernate-mapping>


No olviden colocar los mapeos en el archivo de configuración "hibernate.cfg.xml" de la siguiente forma:

<mapping resource="hibernate/herencia/mapeos/Persona.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Normal.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tecnologo.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Programador.hbm.xml" />
<mapping resource="hibernate/herencia/mapeos/Tester.hbm.xml" />


Igual que en los casos anteriores, podemos poner todos estos mapeos en un solo archivo, de la siguiente forma:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="hibernate.herencia.modelo.Persona" abstract="true">
        <id name="id">
            <generator class="assigned" />
        </id>

        <property name="nombre" />
        <property name="edad" />
        
        <union-subclass name="hibernate.herencia.modelo.Normal" table="normales">
            <property name="ocupacion" />
        </union-subclass>

        <union-subclass name="hibernate.herencia.modelo.Tecnologo" table="tecnologos">
            <property name="aniosDeEstudios" />
        </union-subclass>
        
        <union-subclass name="hibernate.herencia.modelo.Programador" table="programadores">
            <property name="lenguajeFavorito" />
            <property name="aniosDeExperiencia" />
        </union-subclass>

        <union-subclass name="hibernate.herencia.modelo.Tester" table="testers">
            <property name="herramientaDeTesteo" />
        </union-subclass>

    </class>
</hibernate-mapping>


Probemos que la configuración ha quedado bien usando nuevamente el siguiente código en nuestro método "main":

public static void main(String[] args)
{
    Normal normal            = new Normal("normal", 21, "Empleado");
    Tecnologo tecnologo      = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester tester            = new Tester("tester", 18, 3, "JUnit");

    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


Al ejecutar nuestra aplicación podemos ver que se generan las siguientes tablas:



Como vemos, solo se crearon tablas para las entidades no-abstractas de nuestra aplicación. Ahora veamos los datos que contiene cada una de estas tablas:



Nuevamente todo ha salido como esperabamos ^-^. Ahora veamos cómo usar esta estrategia usando anotaciones.


Una Tabla por Clase Concreta Usando Anotaciones


En esta estrategia nuevamente hacemos uso de la anotación "@Inheritance" en la clase base (en este caso "Persona") para indicar el mecanismo de herencia que queremos usar. En este caso colocaremos en su atributo "strategy" el valor "InheritanceType.TABLE_PER_CLASS", de la siguiente forma:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Persona implements Serializable
{
}


Lo que sigue es indicar cuál de los atributos de la clase servirá como identificador. Igual que ocurre en el caso de usar archivos de mapeo, en esta ocasión no podemos usar el valor "GenerationType.IDENTITY" como la estrategia de generación del identificador. De hecho en esta ocasión el único valor que podemos elegir es:

  • GenerationType.TABLE

Este tipo de generación lo que hace es crear una tabla especial para generar los identificadores (una especie de secuencia) por lo que en nuestra base de datos terminaremos con una tabla de más.

Fuera de esta pequeña diferencia el resto del mapeo de nuestra clase no tiene ninguna otra particularidad. El mapeo completo de la clase "Persona" se muestra a continuación:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Persona implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.TABLE)
    private long id;
    private String nombre;
    private int edad;

    public Persona()
    {
    }

    public Persona(String nombre, int edad)
    {
        this.nombre = nombre;
        this.edad = edad;
    }

    public int getEdad()
    {
        return edad;
    }

    public void setEdad(int edad)
    {
        this.edad = edad;
    }

    public long getId()
    {
        return id;
    }

    protected void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }
}


El mapeo de las subclases no tiene ninguna cosa extraña, por lo que solo colocaré el mapeo de "Normal", que queda de la siguiente forma:

@Entity
public class Normal extends Persona
{
    private String ocupacion;

    public Normal()
    {
    }

    public Normal(String nombre, int edad, String ocupacion)
    {
        super(nombre, edad);
        this.ocupacion = ocupacion;
    }

    public String getOcupacion()
    {
        return ocupacion;
    }

    public void setOcupacion(String ocupacion)
    {
        this.ocupacion = ocupacion;
    }
}


El mapeo de las demás clases es exactamente igual que el de "Normal". No olviden colocar estas clases en el archivo de configuración "hibernate.cfg.xml":

<mapping class="hibernate.herencia.modelo.Persona"/>
<mapping class="hibernate.herencia.modelo.Normal"/>
<mapping class="hibernate.herencia.modelo.Tecnologo"/>
<mapping class="hibernate.herencia.modelo.Programador"/>
<mapping class="hibernate.herencia.modelo.Tester"/>


Ahora, probemos que nuestra aplicación funciona usando nuevamente el siguiente código en nuestro método "main":

public static void main(String[] args)
{
    Normal normal            = new Normal("normal", 21, "Empleado");
    Tecnologo tecnologo      = new Tecnologo("tecnologo", 24, 4);
    Programador programador1 = new Programador("primer programador", 25, 4, "java", 4);
    Programador programador2 = new Programador("segundo programador", 25, 5, "java", 2);
    Tester tester            = new Tester("tester", 18, 3, "JUnit");

    AbstractDAO.almacenaEntidad(normal);
    AbstractDAO.almacenaEntidad(tecnologo);
    AbstractDAO.almacenaEntidad(programador1);
    AbstractDAO.almacenaEntidad(programador2);
    AbstractDAO.almacenaEntidad(tester);
}


Al ejecutar nuestra aplicación podemos ver que se generan las siguientes tablas:



En la imagen anterior podemos ver que se generó, además de las tablas esperadas, una tabla llamada "hibernate_sequences". Esta tabla es la que usa para generara los identificadores de cada una de las subclases de "Persona" que guardemos en la base de datos. Ahora veamos los datos almacenados en cada uno de nuestras tablas:



Podemos ver que, nuevamente, los datos se han almacenado correctamente y que todo ha salido como lo planeamos ^_^.

Veamos, solo por curiosidad, nuevamente el SQL generado por Hibernate en las consultas polimórficas. Si intentamos recuperar una instancia de la clase "Programador" usando la siguiente línea al final del código anterior:

AbstractDAO.getEntidad(programador1.getId(), Programador.class);


Hibernate genera el siguiente SQL:

select programado0_.id as id0_0_, programado0_.edad as edad0_0_, programado0_.nombre as nombre0_0_, programado0_.aniosDeEstudios as aniosDeE1_2_0_, programado0_.aniosDeExperiencia as aniosDeE1_3_0_, programado0_.lenguajeFavorito as lenguaje2_3_0_ from Programador programado0_ where programado0_.id=?


Que acomodado queda de la siguiente forma:

SELECT
  prog.id as id0_0_, prog.edad as edad0_0_, prog.nombre as nombre0_0_, prog.aniosDeEstudios as aniosDeE1_2_0_, prog.aniosDeExperiencia as aniosDeE1_3_0_, prog.lenguajeFavorito as lenguaje2_3_0_ 
  
FROM 
  Programador prog 

WHERE prog.id=?


Como podemos ver no hay ningún problema, pedimos un Programador y Hibernate lo busca en la tabla "programador". ¿Pero qué ocurre si no sabemos a cuál clase pertenece nuestra entidad? En ese caso tendríamos que usar la siguiente línea:

AbstractDAO.getEntidad(programador1.getId(), Persona.class);


Con lo que Hibernate generará la siguiente consulta:

select persona0_.id as id0_0_, persona0_.edad as edad0_0_, persona0_.nombre as nombre0_0_, persona0_.ocupacion as ocupacion1_0_, persona0_.aniosDeEstudios as aniosDeE1_2_0_, persona0_.aniosDeExperiencia as aniosDeE1_3_0_, persona0_.lenguajeFavorito as lenguaje2_3_0_, persona0_.herramientaDeTesteo as herramie1_4_0_, persona0_.clazz_ as clazz_0_ from ( select id, edad, nombre, null as ocupacion, aniosDeEstudios, aniosDeExperiencia, lenguajeFavorito, null as herramientaDeTesteo, 3 as clazz_ from Programador union select id, edad, nombre, null as ocupacion, aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, herramientaDeTesteo, 4 as clazz_ from Tester union select id, edad, nombre, ocupacion, null as aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, null as herramientaDeTesteo, 1 as clazz_ from Normal union select id, edad, nombre, null as ocupacion, aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, null as herramientaDeTesteo, 2 as clazz_ from Tecnologo ) persona0_ where persona0_.id=?


Que un poco acomodada se ve así:

SELECT 
  pers.id as id0_0_, pers.edad as edad0_0_, pers.nombre as nombre0_0_, pers.ocupacion as ocupacion1_0_, pers.aniosDeEstudios as aniosDeE1_2_0_, pers.aniosDeExperiencia as aniosDeE1_3_0_, pers.lenguajeFavorito as lenguaje2_3_0_, pers.herramientaDeTesteo as herramie1_4_0_, pers.clazz_ as clazz_0_ 
  
  
FROM ( 
  SELECT id, edad, nombre, null as ocupacion, aniosDeEstudios, aniosDeExperiencia, lenguajeFavorito, null as herramientaDeTesteo, 3 as clazz_ 
  FROM Programador 
  
  UNION 
  
  SELEC id, edad, nombre, null as ocupacion, aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, herramientaDeTesteo, 4 as clazz_ 
  FROM Tester 
      
  UNION 
      
  SELECT id, edad, nombre, ocupacion, null as aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, null as herramientaDeTesteo, 1 as clazz_ 
  FROM Normal 
      
  UNION 
      
  SELECT id, edad, nombre, null as ocupacion, aniosDeEstudios, null as aniosDeExperiencia, null as lenguajeFavorito, null as herramientaDeTesteo, 2 as clazz_ 
  FROM Tecnologo ) pers 
      
WHERE pers.id=?


Como podemos ver, Hibernate tiene que buscar, nuevamente, a qué clase pertenece el objeto en todas las tablas para la jerarquía de clases. Esto, como se podrán imaginar, hará nuestras consultas muy lentas en caso de que dicha jerarquía tenga muchas clases.

Hemos podido ver cómo usar las tres estrategias de herencia que Hibernate nos proporciona y las ventajas y desventajas de algunas de ellas. Aunque no es necesario que usemos solo una estrategia en nuestras aplicaciones, podemos mezclarlas en la forma que más nos convenga para sacarles el mayor provecho.

Para terminar este tutorial les dejo algunos consejos de cómo elegir una estrategia de herencia para sus aplicaciones.


Eligiendo una Estrategia de Herencia


  • Si no requerimos asociaciones o consultas polimórficas, lo mejor será elegir "una tabla por clase concreta"; en otras palabras, si nunca o rara vez haremos una consulta buscando con la clase base (como "Persona"), entonces esta estrategia es ideal, ya que solo buscaremos en una tabla de nuestra base de datos.
  • Si requerimos asociaciones o consultas polimórficas y nuestras subclases declaran pocos atributos (esto puede ser debido a que la diferencia principal entre las subclases sea el comportamiento), entonces lo mejor será elegir "una tabla para la jerarquía de clases".
  • Finalmente, si requerimos asociaciones polimórficas y nuestras subclases declaran muchos atributos distintos entonces la mejor estrategia será "una tabla por subclase". O, dependiendo de de la profundidad de la jerarquía de herencia y el posible costo de los joins contra las uniones, podríamos elegir "una tabla por clase concreta".

Por default podemos elegir "una tabla por jerarquía de clases" para problemas simples, y en casos más complejos elegir alguna de las otras dos estrategias, o considerar modificar nuestro modelo de datos ^-^!

Pues bien, esto es todo para este post. En la siguiente y última entrega hablaré sobre cómo trabajar con Interceptores y Eventos.

Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas: