0

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)
Royce
  • 1,557
  • 5
  • 19
  • 44
  • I'm a bit confused by your question description, are you planning to have a single table called `Oeuvre` or are you going to have two tables, one for `Livre` and another one for `Magasine`? – g00glen00b Mar 01 '19 at 12:59
  • To be honest, I don't know what is the best way to do it, can you advise me? – Royce Mar 01 '19 at 13:04
  • If you don't want those `null` issues, and you want to define a constraint for `Livre`, then I think you answered your own question, and you want a separate table for `Livre` and `Magasine`. – g00glen00b Mar 01 '19 at 13:07
  • I can't create three entity ? `Oeuvre`, `Livre` and `Magasine`? Because if I create only `Livre` and `Magasine` I can't create constraints as `ALTER TABLE exemplaire ADD FOREIGN KEY (idoeuvre) REFERENCES oeuvre(idoeuvre) ON UPDATE CASCADE` right? `Exemplaire` is an entity linked to `Oeuvre` – Royce Mar 01 '19 at 13:23
  • @g00glen00b I edited my post. – Royce Mar 01 '19 at 14:12

1 Answers1

1

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.

g00glen00b
  • 41,995
  • 13
  • 95
  • 133
  • Thanks for this very clear answer. However, I have a question, with the second solution, I can't know, from an `Oeuvre` if it's an instance of `Livre` or `Magasine` because they are not extended from `Oeuvre`. Is there a solution for that using `@MappedSuperclass`? Or a sql request is needed? – Royce Mar 01 '19 at 15:03
  • @N.Lamblin You can use bi-directional mapping for that by using the `@OneToOne(mappedBy = "oeuvre")` annotation within `Oeuvre`. I've edited my answer with an example. Behind the screens this will execute the additional SQL request for you, or apply a join when possible. – g00glen00b Mar 01 '19 at 15:13
  • Thanks :) The last thing I need is have to the same id in `oeuvre / livre` and `oeuvre / exemplaire`. Indeed, it is a bit stupid to have 2 differents id... To do that can I use constructor like `Livre(String idoeuvre, Auteur auteur) { this.idlivre = idoeuvre; ...}` ? Or definied `idoeuvre` as @id ? – Royce Mar 01 '19 at 15:45
  • @N.Lamblin That isn't as easy, each entity must have an `@Id` field, and if you don't want to lose the `@OneToOne` mapping to `Oeuvre`, you'll have to map `idoeuvre` twice. However, Hibernate won't allow you, unless you define one of them as `updatable = false` and `insertable = false`. You can read more about that here: https://stackoverflow.com/a/3807285 – g00glen00b Mar 01 '19 at 15:49
  • Ok so I will keep `idlivre` and `idoeuvre` and add `updatable = false, insertable = false` on `idoeuvre` even if I find that a bit dirty ahah. Thanks! – Royce Mar 01 '19 at 16:40