Héritage

2 classes concrètes Movie et TVShow,  héritant des attributs de la classe mère WatchBall.

Méthode 1: une table par table concrète

@MappedSuperclass
public abstract class Watchable {
    // Pas de Id pour cette classe mere

    @Column(nullable = false)
    protected String name;

    protected String description;

    // ... Getters et setters
}
Langage du code : PHP (php)
@Entity
public class Movie extends Watchable {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

        private Certification certification;

	// ...
}

Langage du code : PHP (php)

@Entity
public class TVShow extends Watchable {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	private int seasons;

	// ...
}
Langage du code : PHP (php)

Table movie
Table tv_show

En base de données deux tables movie et tv_show et la classe Watchable n’aura pas de représentation
Les attributs name et description de Watchable seront ajoutés comme colonnes dans les tables enfants movie et tv_show

L’attribut Id de Watchable est reporté dans les classes enfants mais on peut le mettre explicitement dans Watchable. Ce sera l’objet de la 2 ième solution.

L’avantage de cette solution, c’est qu’elle est assez simple à mettre en œuvre.

Les Inconvénients de cette solution:

Nous aurons un select par classe concrète.
Un autre inconvénient aussi, c’est que si ici un attribut change, il va falloir répercuter ces changements sur toutes les tables des classes concrètes.

Et enfin, un dernier inconvénient pour cette solution, c’est tout ce qui est association.
En effet, vous ne pourrez pas mettre d’association dans la classe Watchable (Exemlpe, Hibernate ne sera pas capable d’associer les reviews avec Watchable)

Méthode 2: TABLE_PER_CLASS – une solution dérivée de la première

l’ID a été remontée au niveau de Watchable et il ne sera plus présent dans Movie et TVShow

Watchable, a maintenant un @Entity et @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
On va définir donc une stratégie table par classe.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Watchable {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	protected Long id;

	@Column(nullable = false)
	protected String name;

	protected String description;

	// 
}Langage du code : PHP (php)

@Entity
public class Movie extends Watchable {

	private Certification certification;

	// ...
	
}Langage du code : PHP (php)

@Entity
public class TVShow extends Watchable {

	private int seasons;

	// ...

}
Langage du code : PHP (php)

Méthode 3: SINGLE_TABLE

Table mère watchable

Pour ce type d’héritage, seule la table mère est représentée en base et la colonne bd-type est une colonne disciminator pour distinguer le type d’enregistrement soit movie ou soit tvshow


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "bd_type")
public abstract class Watchable {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	protected Long id;

	@Column(nullable = false)
	protected String name;

	protected String description;

	// ...
}Langage du code : PHP (php)
@Entity
@DiscriminatorValue("movie")
public class Movie extends Watchable {

	private Certification certification;
        // ...
}Langage du code : PHP (php)
@Entity
@DiscriminatorValue("tvshow")
public class TVShow extends Watchable {

	private int seasons;
        // ...
}Langage du code : PHP (php)

Faison maintenant un test sql reliant la table mère watchable evec review

	@Test
	public void inheritance_getAll() {
		List<Watchable> all = entityManager
				.createQuery("select distinct w from Watchable w left join fetch w.reviews", Watchable.class)
				.getResultList();

		assertThat(all).hasSize(3);
		assertThat(all.stream().filter(o -> o.getClass() == TVShow.class)).hasSize(1);
		assertThat(all.stream().filter(o -> o.getClass() == Movie.class)).hasSize(2);
	}
Langage du code : JavaScript (javascript)

Et voici la requête générée par Hibernate:

select distinct watchable0_.id            as id2_4_0_,
                reviews1_.id              as id1_3_1_,
                watchable0_.description   as descript3_4_0_,
                watchable0_.name          as name4_4_0_,
                watchable0_.certification as certific5_4_0_,
                watchable0_.seasons       as seasons6_4_0_,
                watchable0_.bd_type       as bd_type1_4_0_,
                reviews1_.author          as author2_3_1_,
                reviews1_.content         as content3_3_1_,
                reviews1_.movie_id        as movie_id4_3_1_,
                reviews1_.movie_id        as movie_id4_3_0__,
                reviews1_.id              as id1_3_0__
from Watchable watchable0_
         left outer join Review reviews1_ on watchable0_.id = reviews1_.movie_idLangage du code : JavaScript (javascript)

Hibernate nous fait juste un select distinct donc avec watchable et le left join sur review parce qu’on lui a demandé.
Il se sert de la colonne disciminant bd_type pour distinguer les lignes enfants soit movie ou soit tvshow.

Cette solution a l’avantage d’être simple à comprendre en termes de code, de bases de données et donc en termes de requêtes SQL.
C’est aussi la solution la plus performante.

Le seul désavantage à cette solution, ce sont les contraintes d’intégrité au niveau de la base de données.
En effet, si on a une colonne qui est propre à l’une des sous classes et qu’on ne veut pas qu’elle soit nulle par exemple, ça ne sera pas possible avec cette solution.


Méthode 4: JOINED – Héritage en BDD et dans le code

Héritage type JOINED

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Watchable {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	protected Long id;

	@Column(nullable = false)
	protected String name;

	protected String description;

	// ...
}
Langage du code : PHP (php)
@Entity
public class Movie extends Watchable {
    private Certification certification;

    // ...
}Langage du code : PHP (php)
@Entity
public class TVShow extends Watchable {
	private int seasons;
        
        // ...
}Langage du code : PHP (php)

En base cela donne:

CREATE TABLE watchable
(
    id          BIGINT       NOT NULL,
    name        VARCHAR(255) NOT NULL,
    description VARCHAR(255),
    CONSTRAINT pk_watchable PRIMARY KEY (id)
);

CREATE TABLE movie
(
    id            BIGINT NOT NULL,
    certification INTEGER,
    CONSTRAINT pk_movie PRIMARY KEY (id)
);

CREATE TABLE tvshow
(
    id      BIGINT  NOT NULL,
    seasons INTEGER NOT NULL,
    CONSTRAINT pk_tvshow PRIMARY KEY (id)
);


// Foreign key watchable_id dans movie et tvshow
ALTER TABLE movie
    ADD CONSTRAINT FK_MOVIE_ON_ID FOREIGN KEY (id) REFERENCES watchable (id);

ALTER TABLE tvshow
    ADD CONSTRAINT FK_TVSHOW_ON_ID FOREIGN KEY (id) REFERENCES watchable (id);Langage du code : PHP (php)


Faison ensuite un test sql en associant la table mère watchable avec celle de review qui elle est en association avec movie et observons la requete générée par Hibernate


@Test
public void inheritance_getAll() {
    List<Watchable> all = entityManager
                .createQuery("select distinct w from Watchable w left join fetch w.reviews", Watchable.class)
                .getResultList();

     assertThat(all).hasSize(3);
     assertThat(all.stream().filter(o -> o.getClass() == TVShow.class)).hasSize(1);
     assertThat(all.stream().filter(o -> o.getClass() == Movie.class)).hasSize(2);
}Langage du code : JavaScript (javascript)
select distinct watchable0_.id                                 as id1_9_0_,
                reviews1_.id                                   as id1_7_1_,
                watchable0_.description                        as descript2_9_0_,
                watchable0_.name                               as name3_9_0_,
                watchable0_1_.certification                    as certific1_3_0_,
                watchable0_2_.seasons                          as seasons1_8_0_,

                case
                    when watchable0_1_.id is not null then 1 -- Movie
                    when watchable0_2_.id is not null then 2 -- TVShow
                    when watchable0_.id is not null then 0   -- Review
                end                                            as clazz_0_,

                reviews1_.author                               as author2_7_1_,
                reviews1_.content                              as content3_7_1_,
                reviews1_.movie_id                             as movie_id5_7_1_,
                reviews1_.rating                               as rating4_7_1_,
                reviews1_.movie_id                             as movie_id5_7_0__,
                reviews1_.id                                   as id1_7_0__
from Watchable watchable0_
         left outer join Movie watchable0_1_ on watchable0_.id = watchable0_1_.id
         left outer join TVShow watchable0_2_ on watchable0_.id = watchable0_2_.id
         left outer join Review reviews1_ on watchable0_.id = reviews1_.movie_idLangage du code : JavaScript (javascript)

Hibernate nous génère systématiquement une jointure avec ses tables enfants movie et tvshow plus la table review parce qu’on l’a demandé.

On remarque le la clause sql case / when utilisé par Hibernate afin d’identifier chaque ligne de résultat distinct par un foreign key d’héritage (movie et tvshow) ou autre(review). Autrement dit, il calcule entre guillemets le discriminant en faisant un case sur la valeur des id.

Par rapport à notre base de données, on s’attend donc à avoir 3 lignes de résultats: 2 movies et un tvshow


Cette dernière solution n’est pas très recommandable car peu performante au niveau des requêtes complexes générées par Hibernate que ce soit pour les selects ou inserts.