Royce
Royce

Reputation: 1595

What is the best way to manage inherit class with JPA?

Context

I have a class Oeuvre, and two sub class Livre and Magasine. An Auteur is linked to a Livre.

So the class diagram looks like :

       Oeuvre
          |
     ____________
    |            |
Magasine        Livre____Auteur

Entities

@Entity
public class Oeuvre {

    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idoeuvre;

    @NotNull
    private String titre;

    @NotNull
    private String ISBN;

    @ColumnDefault("CURRENT_DATE()")
    private LocalDate parution;

}

@Entity
public class Magasine extends Oeuvre {

    @Enumerated(EnumType.STRING)
    private Type type;
}

@Entity
public class Livre extends Oeuvre {

    @ManyToOne
    @JoinColumn(name = "idauteur")
    private Auteur auteur;    
}

@Entity
public class Auteur {

    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idauteur;

    @NotNull
    private String nom;

    @NotNull
    private String prenom;
}

H2 representation

Oeuvre(idoeuvre, isbn, parution, titre, type, idauteur)

But with this representation I can have empty values. For example idauteur will be null if Oeuvre is a Magasine.

So, this representation is not better?

Oeuvre(idoeuvre, isbn, parution, titre)
Livre(idlivre, idauteur, idoeuvre)
Magasine(idmagasine, type, idoeuvre)

SQL script

With the current database scheme I can't create this constraint : ALTER TABLE livre ADD FOREIGN KEY (idauteur) REFERENCES auteur(idauteur) ON UPDATE CASCADE

Conclusion

Is it possible to represent it with JPA/H2?

Thanks for your answers.

EDIT 1 : Question

Is it possible to have this database scheme with JPA / H2?

Oeuvre(idoeuvre, isbn, parution, titre)
Livre(idlivre, idauteur, idoeuvre)
Magasine(idmagasine, type, idoeuvre)

Upvotes: 0

Views: 92

Answers (1)

Dimitri Mestdagh
Dimitri Mestdagh

Reputation: 44745

The way you described your entities, Hibernate will expect the following database structure:

oeuvre(idoeuvre, titre, isbn, parution)
magasine(idoeuvre, titre, isbn, parution, type)
livre(idoeuvre, titre, isbn, parution, idauteur)

This will lead to a lot of duplication, and is probably not what you want. Now, you did describe two possible solutions though, namely:

  1. Defining one table called oeuvre, where each field is present, for example:

    oeuvre(idoeuvre, titre, isbn, parution, type, idauteur)
    
  2. Definining separate tables for livre and magasine, for example:

    oeuvre(idoeuvre, titre, isbn, parution)
    magasine(idmagasine, idoeuvre, idauteur)
    livre(idlivre, idoeuvre, idauteur)
    

Now, as you did mention, the first solution doesn't allow you to define a constraint, while the second one does. Basically, that means you provided your own answer, if you need a constraint, then you'll have to differentiate between the different types, and then you'll have to go for the second approach.

You could then define the following constraints:

  • Each magasine.idoeuvre and livre.idoeuvre should be unique (you could even use them as a primary key)
  • livre.idauteur should be not null

However, in this case, you don't need any inheritance. Inheritance within Hibernate is usually only done if multiple tables have the same fields, while in this case you have three separate tables, and thus three separate entities, no inheritance involved:

@Entity
public class Oeuvre {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idoeuvre;

    @NotNull
    private String titre;

    @NotNull
    private String ISBN;

    @ColumnDefault("CURRENT_DATE()")
    private LocalDate parution;
}

@Entity
public class Magasine { // No extends
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idmagasine;

    @OneToOne
    @JoinColumn(name = "idoeuvre")
    private Oeuvre oeuvre;

    @Enumerated(EnumType.STRING)
    private Type type;
}

@Entity
public class Livre { // No extends
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idlivre;

    @OneToOne
    @JoinColumn(name = "idoeuvre")
    private Oeuvre oeuvre;

    @ManyToOne
    @JoinColumn(name = "idauteur")
    private Auteur auteur;   
}

The only reason why you could use inheritance in this case is because both Magasine and Livre both have a similar Oeuvre mapping, in that case, you could change your code like this:

@Entity
public class Oeuvre {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idoeuvre;

    @NotNull
    private String titre;

    @NotNull
    private String ISBN;

    @ColumnDefault("CURRENT_DATE()")
    private LocalDate parution;
}

@MappedSuperclass // No @Entity
public class SpecificOeuvre {
    @OneToOne
    @JoinColumn(name = "idoeuvre")
    private Oeuvre oeuvre;
}

@Entity
public class Magasine extends SpecificOeuvre {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idmagasine;

    @Enumerated(EnumType.STRING)
    private Type type;
}

@Entity
public class Livre extends SpecificOeuvre {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idlivre;

    @ManyToOne
    @JoinColumn(name = "idauteur")
    private Auteur auteur;   
}

Both represent the same database structure, the only difference is that one of them has a separate class annotated with @MappedSuperclass that contains all re-usable information between Magasine and Livre, which in this case is only the mapping to Oeuvre.

If you want to know whether or not an Oeuvre is linked to a Livre or a Magasine, you can use bi-directional mapping, for example:

@Entity
public class Oeuvre {
    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    private String idoeuvre;

    @NotNull
    private String titre;

    @NotNull
    private String ISBN;

    @ColumnDefault("CURRENT_DATE()")
    private LocalDate parution;

    @OneToOne(mappedBy = "oeuvre")
    private Livre livre;

    @OneToOne(mappedBy = "oeuvre")
    private Magasine magasine;
}

Now you can see if oeuvre.getMagasine() is given or oeuvre.getLivre() is given.

Upvotes: 1

Related Questions