En Java, la herencia es una de los conceptos principales. El hecho es que transformar esta herencia en mapeo de Hibernate puede resultar un problema porque las bases de datos relacionales no cuentan con un proceso para realizar la operación, es decir, SQL, Hibernate o cualquier otro ORM no soporta este tipo de mapeo.
Para poder solucionar este problema, JPA (Java Persistance API) tiene ciertas estrategias para abordar esta problemática.
Para poder resolver la problemática, JPA abordó el problema con varias soluciones:
Mapeo superclase
Esta estrategia de mapeo es el enfoque más simple para asignar una estructura de herencia a las tablas de la base de datos. Se establece que la clase padre no puede ser una entidad. Por ejemplo, si tenemos una superclase "Persona":
@MappedSuperclass
public class Persona {
@Id
private long idPersona;
private String nombre;
//constructor, getters, setters
}
@Entity
public class Alumno extends Persona {
private String dni;
//constructor, getters, setters
}
Tendremos que añadir la anotación @MappedSuperclass, que indicará que esa clase no se mapea porque es una superclase. En cambio, la clase "Alumno" es una entidad porque extiende de "Persona". Por eso, a esa subclase deberemos añadirle la etiqueta @Entity.
En la base de datos, la clase "Alumno" equivaldrá a la tabla "Alumno" y tendrá tres columnas: las dos primeras de la superclase y la de la subclase. Por tanto, la representación en la base de datos será con los datos de la superclase más los de la entidad.
Asigna cada clase concreta a su propia tabla. Eso permite compartir la definición de atributo entre varias entidades, pero también supone un gran inconveniente: el mapeo de una superclase no es una entidad, y no hay una tabla para ello.
Tabla única
Esta estrategia es muy similar a la anterior, la principal diferencia es que ahora la superclase también es una entidad. La estrategia de tabla única crea una tabla para cada jerarquía de clase, la superclase y las subclases estarán dentro de la misma tabla. Esta es también la estrategia predeterminada elegida por JPA si no especificamos una explícitamente. Eso hace que la consulta para una clase específica sea fácil y eficiente.
Esta estrategia se centrará en agrupar las clases padre e hijo en una misma tabla. Dado que los registros de esta jerarquía de clases estarán en la misma tabla, Hibernate necesita una forma de identificar a qué entidad pertenece cada registro.
@Entity(name="articulos")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="tipo_articulo",
discriminatorType = DiscriminatorType.INTEGER)
public class Articulo{
//atributos,constructor, getters, setters
}
Como podemos apreciar por el ejemplo, la clase padre se mapeará como una entidad y se añadirá la etiqueta @Inheritance, definiendo la estrategia como InheritanceType.SINGLE_TABLE. Esta definición indicará que la estrategia de herencia será de tabla única y es necesario añadirla a toda clase que sea superclase. Ahora deberemos definir qué columna se utilizará para distinguir a qué clase pertenece cada registro. Para ello utilizamos la etiqueta @DiscriminatorColumn y le asignamos un nombre "tipo_articulo". Aquí se añadirá el nombre de la clase y se podrá filtrar cuando se haga una consulta. Si no se especifica el nombre, Hibernate establecerá esa columna como DTYPE.
Para las subclases, se añadirá solo la anotación @Entity y la anotación @DiscriminatorValue.
@Entity
@DiscriminatorValue("1")
public class Lapiz extends Articulo{
//atributos,constructor, getters, setters
}
@Entity
@DiscriminatorValue("2")
public class Libreta extends Articulo{
//atributos,constructor, getters, setters
}
En la definición de las subclases, se debe añadir la anotación @DiscriminatorValue, que especificará el valor que discriminar para esa entidad. Es decir, el valor 1 indicará que la clase a la que pertenece ese registro es 1, y el 2 hará referencia a la clase "Libreta". Es una manera simple de identificar la clase a la que pertenecen, pero también se puede indicar que la columna sea un string y asignar un valor de texto. En lugar de poner discriminatorType=DiscriminatorType.INTEGER, se pondría discriminatorType=DiscriminatorType.STRING.
Este tipo de estrategia es una manera eficiente y fácil de acceder a los datos de la base de datos. Todos los atributos de cada entidad se guardan en una misma tabla, y las consultas no necesitan de joins para ejecutarse. La única particularidad es que Hibernate necesita añadir a la consulta SQL una comparación del valor discriminador para obtener la entidad.
Joined table
Esta estrategia, a diferencia de la anterior, se encarga de mapear cada clase en una tabla propia. La única columna que se repite es el identificador, que se utilizará para enlazar las tablas.
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {
@Id
private long idAnimal;
private String especie;
// constructor, getters, setters
}
En la clase padre, deberemos definir la clase con la etiqueta @Entity y en strategy de la etiqueta @Inheritance deberemos añadir InheritanceType.JOINED.
Para la subclase se definirá así:
@Entity
@PrimaryKeyJoinColum("idGato")
public class Gato extends Animal {
private String nombre;
// constructor, getters, setters
}
Las dos clases tendrán un identificador "idAnimal". La clave primaria de la entidad "Gato" también tiene una restricción de clave externa para la clave primaria de su clase padre. Para personalizar esta columna, podemos agregar la anotación @PrimaryKeyJoinColumn y añadir el nombre de la columna.
La desventaja de este tipo de estrategia es que la recuperación de entidades requiere uniones entre tablas, lo que puede resultar en un menor rendimiento para grandes cantidades de registros. El número de combinaciones es mayor cuando se consulta la clase principal, ya que se unirá con cada elemento secundario relacionado, por lo que es más probable que el rendimiento se vea afectado en la superclase de la que queremos recuperar registros.