Las Bases de Datos relacionales no tienen una manera de tener herencia entre tablas, para solucionar esto, JPA proporciona una serie de estrategias y nos encamina a una herencia con Hibernate.

Estrategias en Herencia con Hibernate

JPA nos proporciona diferentes estrategias para lograr la herencia:

  • MappedSuperclass: La clase padre no puede ser una entidad
  • Single Table – las entidades que son de diferentes clases y tienen un padre común se colacan en una sola tabla.
  • Joined Table – cada clase tiene su propia tabla y realizar queries sobre subclases require join de tablas.
  • Table-Per-Class – todas las propiedades se encuentran en la tabla, así que no se requiere ninguna join.

Aplicar herencia con Hibernate en estas entidades nos permite trabajar con polimorfismo entre clases. Cada una de las estrategias anteriores, dará como resultado una estructura en base de datos diferente.

Estrategia MappeSuperClass

Con esta estrategia de herencia, lo que buscamos en añadir en nuestra tabla de base de datos las columnas que heredamos. Y la clase padre tendrá que llevar la anotación @MappedSuperClass vamos a verlo con un ejemplo:

@MappedSuperclass
@Getter
@Setter
public class Car {
 
    @Id
    private Long id;
    private String color;
 
}
@Getter
@Setter
@Entity
public class Model extends Car {
 
    private String model;
 
}

Con esta última clase, lo que hemos hecho ha sido heredar de la Clase Car, con lo que tenemos sus atributos. En Base de Datos tendremos una tabla Model con tres columnas. Hay que tener en cuenta que la clase Padre no lleva @Entity.

Estrategia  Joined Table

En esta estrategia, la herencia es definida en la clase abstracta padre para ello vamos a usar la anotación @Inheritance. Con esta estrategia y a diferencia de la anterior, todas las clases hijas tendrán su propia tabla.

Tanto en la tabla padre como en la que hereda se tendrá el id de la tabla padre, es decir, tendrá una columna haciendo referencia. Se puede utilizar la anotación @PrimaryKeyJoinColumn para customizar este identificador. El campo que sea la clave (key), se definirá únicamente en la clase padre.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
@Setter
public class Vehicle {
    @Id
    private long vehicleId;
    private String model;
 
}

En la clase anterior hemos definido el tipo de estrategia a utilizar, y las clases hijos extenderán de aquí.

@Entity
@Getter
@Setter
public class Car extends Vehicle {
    private String name;
 
}

Esta clase Car extiende de Vehicle, ambas clases crearán una tabla en Base de Datos. Ambas tablas tendrán la columna vehicleId, y la tabla Car tendrá una foreign key constraint a la tabla padre.

Tal y como hemos dicho un poco más arriba podemos utilizar la anotación, PrimaryKeyJoinColumn, para poder cambiar el nombre de la columna que se hereda.

@Entity
@Getter
@Setter
@PrimaryKeyJoinColumn(name = "carId")
public class Car extends Vehicle {

}

La mayor desventaja de esta aproximación, y que va ligado con las bases de datos relacionales, es que se van a ir haciendo joins con las tablas hijas, y esto penaliza mucho en las búsquedas cuando se tienen muchos registros.

Estrategia Tabla por Clase

En esta estrategia, vamos a tener una tabla en Base de Datos por cada clase que se cree con su entidad, incluyendo las heredadas.

El esquema resultante es similar al que usa @MappedSuperclass, con la diferencia que la clase padre también es una tabla, repitiendo tantas veces como se herede los mismos atributos.

Para poder utilizar esta estrategia es necesario definirla así, @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Getter
@Setter
public class Vehicle {
    @Id
    private Long vehicleId;
 
    private String model;
 
}

Esta estrategia además usa SQL UNION. Cuando no se conoce la subclase concreta, el objeto relacionado podría estar en cualquiera de las tablas de la subclase, lo que hace que las uniones a través de la relación sean imposibles, por lo tanto, proporciona un soporte deficiente para las relaciones polimórficas.

Estrategia Single Table

En este caso, se crea una tabla para cada jerarquía de la clases. Esta va a ser la estrategia que se utiliza por defecto en JPA si no se especifica.

El identificador esta definido en la clase Padre, y podemos extender y heredar de el.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Getter
@Setter
public class vehicle {
    @Id
    private Long vehicleId;
    private String name;
     
}
@Entity
public class Motorbike extends Vehicle {
    private String color;
}
@Entity
public class Car extends Vehicle {
    private String colorDoors;
}

Discriminator Values

Ya que todos los registros van a estar en las mismas tablas, le tenemos que específicar a Hibernate una manera de distinguirlos, y es aquí cuando entra en jugo los Discriminator Values.

Por defecto, esta discriminación se suele realizar a través una columna llamada DTYPE, la cual tendrá el nombre de la entidad como valor. Pero por lo general vamos a querer customizar esta columna, por lo que vamos a usar la anotación @DiscriminatorColumn.

@Entity(name="vehicles")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="vehicle_type", 
  discriminatorType = DiscriminatorType.INTEGER)
public class Vehicle {
    // ...
}

Con la definición de la clase anterior hemos decidido diferenciar por la columna vehicle_type en lugar de por DTYPE. A continuación le decimos a Hibernate como hacer esa diferencia.

@Entity
@DiscriminatorValue("1")
public class Motorbike extends Vehicle {
    private String color;
}
@Entity
@DiscriminatorValue("2")
public class Car extends Vehicle {
    private String colorDoors;
}

Con la definición del @DiscriminatorValue en las entidades anteriores hemos conseguido diferenciar que tipo de objeto será con la columna vehicle_type.

También se nos da la posibilidad de usar en lugar de @DiscriminatorColumn usar @DiscriminatorFormula, en donde en lugar de añadir valores se específica una formula por ejemplo:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when color is not null then 1 else 2 end")
public class Vehicle {
 ... 
}

Con el objeto anterior hemos conseguido guardar un registro con una condición, es decir si el atributo color (de la clase Motorbike) no es null, se guardará con valor 1 la columna vehicle_type.

En esta estrategia vamos a tener la ventaja del rendimiento al hacer queries polimórficas, ya que solo se accede a una tabla.

Conclusión

En este artículo hemos visto las diferentes estrategias que podemos aplicar para utilizar herencia con Hibernate. Cuando vayamos a utilizar cualquier de estas estrategias, habrá que analizar cual es el objetivo, ya que el rendimiento se podría ver muy afectado en función de la estrategia elegida. Si quieres sacar un poco más de provecho a las anotaciones de Hibernate quizás te interese los soft delete con Hibernate.


1 pensamiento sobre “Herencia con Hibernate

Deja una respuesta

Tu dirección de correo electrónico no será publicada.