Ressource

En OO il est naturel d’avoir des classes qui héritent les unes des autres, tandis qu’en base de données relationnelle une table ne va pas hériter d’une autre table; Problème d’Impedance mismatch

Héritage d’entité

@Entity
public class User {
    @Id
    private int id
    private String name;
}
 
@Entity
public class Employee extends User {
    // Pas besoin de id, elle est hérité
    private int salary
}

Ce code Java est valide, mais il ne dit rien de la façon dont ces deux entités seront stockées. C’est tout l’enjeu : il n’existe pas de traduction évidente de l’héritage vers le modèle relationnel, seulement trois compromis possibles.

L’annotation @Inheritance

La stratégie se déclare sur la classe racine de la hiérarchie, et s’applique à toute sa descendance.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class User {
 
}

Note

En l’absence d’annotation @Inheritance, JPA applique la stratégie SINGLE_TABLE par défaut.

Les 3 stratégies de mapping

Reprenons User et Employee, et regardons le schéma produit par chacune.

SINGLE_TABLE — une seule table pour toute la hiérarchie, plus une colonne discriminante

user : ID | DTYPE | NAME | SALARY

JOINED — une table par classe, reliées par une clé primaire partagée (relation 1:1, un employee dépend d’un user pour exister)

user     : ID | NAME
employee : ID | SALARY

TABLE_PER_CLASS — une table complète et autonome par classe concrète, les colonnes héritées étant dupliquées

user     : ID | NAME
employee : ID | NAME | SALARY

Comparaison

SINGLE_TABLEJOINEDTABLE_PER_CLASS
Nombre de tables11 par classe1 par classe concrète
persist d’une fille1 INSERT1 INSERT par niveau1 INSERT
find sur une fille1 SELECTjointure1 SELECT
Requête polymorphique1 SELECTLEFT JOIN sur toutes les tablesUNION sur toutes les tables ❌
Pagination✅ index exploité✅ piloté par la table racine⚠️ tri sur le résultat de l’UNION
Contraintes NOT NULL❌ impossibles sur les filles
Modèle normalisé❌ colonnes creuses❌ colonnes dupliquées
Clé étrangère vers la racine❌ impossible

Comment choisir

  • SINGLE_TABLE si les classes filles ajoutent peu d’attributs et que les lectures priment. C’est le défaut, et souvent le bon choix.
  • JOINED si les classes filles ont de nombreux attributs obligatoires et que l’intégrité doit être garantie par la base.
  • TABLE_PER_CLASS rarement — et dans ce cas, se demander d’abord si un @MappedSuperclass ne conviendrait pas mieux.

Ces trois stratégies supposent que la classe mère soit une entité. Si ce n’est pas le cas — si l’on veut seulement factoriser des attributs communs sans jamais interroger la classe mère — c’est vers @MappedSuperclass qu’il faut se tourner.