@OneToOne ;
@ManyToOne ;
@OneToMany ;
@ManyToMany.
Commune et des
Maire. Chaque commune possède un
maire, et un
maire ne peut être
maire que d'une seule
commune. Nous avons donc bien une relation 1:1 entre les communes et leurs maires. Supposons que dans notre exemple, ce sont les communes qui tiennent la relation entre les communes et les maires.
Exemple 7. Relation 1:1 unidirectionnelle
// // Entité Maire // @Entity public class Maire implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String nom ; // suite de la classe } // // Entité Commune // @Entity public class Commune implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String nom ; @OneToOne private Maire maire ; // suite de la classe }
Maire ne nous permet de connaître la commune dont il est maire. Ce cas est le plus simple à traiter : il suffit d'annoter la relation
maire avec
@OneToOne.
En base, les choses se passent assez simplement : une colonne
maire_id va être créée dans la table
Commune. Cette colonne sera une clé étrangère référençant la colonne
id de la table
Maire. On peut imposer le nom de cette colonne en ajoutant l'annotation
@JoinColumn(name="...") sur la relation
maire.
L'annotation
@OneToOne permet de préciser deux comportements importants dans la gestion des relations en JPA : le comportement
cascade
et le comportement
fetch
, que nous verrons à la fin de cette partie.
@OneToOne et un attribut
mappedBy, qui doit référencer le champ qui porte la relation côté maître. Créons par exemple un champ retour dans notre classe
Maire. L'attribut
mappedBy est défini sur l'entité esclave de la relation.
Exemple 8. Relation 1:1 bidirectionnelle
// // Entité Maire // @Entity public class Maire implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String nom ; @OneToOne(mappedBy="maire") // référence la relation dans la classe Commune private Commune commune ; // suite de la classe }
Collection dans la classe maître. La classe esclave ne porte pas de relation retour. Cette relation peut être spécifiée soit par l'annotation
@OneToMany ou
@ManyToMany.
Elle peut être unidirectionnelle ou bidirectionnelle. Dans ce second cas, le côté maître est obligatoirement le côté qui tient la relation monovaluée.
Exemple 9. Relation 1:p unidirectionnelle
// // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // reste de la classe } // // Entité Bateau // @Entity public class Bateau implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany private Collection<Marin> marins ; // reste de la classe }
@OneToMany ou
@ManyToMany pour spécifier cette relation. Cela dit, il faut noter que les deux annotations ne sont pas équivalente. Dans le premier cas, JPA ajoute une contrainte d'unicité sur la clé étrangère de la table de jointure vers la table
Marin. C'est bien ce que nous voulons ici : un même marin ne peut pas appartenir à plusieurs équipages à la fois. Il faut toutefois bien avoir présent à l'esprit cette contrainte : une relation de type 1:p est parfois une relation
@ManyToMany.
mappedBy sur la relation.
JPA nous pose une contrainte ici : l'attribut
mappedBy est défini pour l'annotation
@OneToMany, mais pas pour l'annotation
@ManyToOne. Or, comme nous l'avons vu dans le cas de l'annotation
@OneToOne,
mappedBy doit être précisé sur le côté esclave d'une relation. Dans le cas d'une relation 1:p bidirectionnelle, JPA ne nous laisse donc pas le choix de l'entité maître et de l'entité esclave.
Exemple 10. Relation 1:p bidirectionnelle
// // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToOne private Bateau bateau ; // reste de la classe } // // Entité Bateau // @Entity public class Bateau implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany(mappedBy="bateau") private Collection<Marin> marins ; // reste de la classe }
Marin), de façon à établir la jointure. Il a de plus créé une clé étrangère vers la clé primaire de la table
Bateau.
Comme on le voit, JPA n'a plus besoin dans ce cas d'une table de jointure pour coder cette relation. On retombe donc dans le cas nominal d'une relation 1:p avec colonne de jointure dans la table cible de la relation.
Exemple 11. Relation p:1 unidirectionnelle
// // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToOne private Bateau bateau ; // reste de la classe } // // Entité Bateau // @Entity public class Bateau implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // reste de la classe }
Marin possède un champ
bateau, sans que cette classe n'ait accès à la liste de son équipage.
Marin, et une clé primaire qui référence la clé primaire de la table
Bateau.
Exemple 12. Relation n:p unidirectionnelle
// // Entité Musicien // @Entity public class Musicien implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToMany private Collection<Instrument> instruments ; // reste de la classe } // // Entité Instrument // @Entity public class Instrument implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // reste de la classe }
@ManyToMany vers l'entité maître. Cette relation doit comporter un attribut
mappedBy, qui indique le nom de la relation correspondante dans l'entité maître.
Exemple 13. Relation n:p bidirectionnelle
// // Entité Musicien // @Entity public class Musicien implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToMany private Collection<Instrument> instruments ; // reste de la classe } // // Entité Instrument // @Entity public class Instrument implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ManyToMany(mappedBy="instruments") private Collection<Musicien> musiciens ; // reste de la classe }
mappedBy qui crée le caractère bidirectionnel de la relation. Si l'on ne le met pas, alors JPA créera une seconde table de jointure.
DETACH,
MERGE,
PERSIST,
REMOVE,
REFRESH.
Le comportement
cascade
consiste à spécifier ce qui se passe pour une entité en relation d'une entité père (que cette relation soit monovaluée ou multivaluée), lorsque cette entité père subit une des opérations définies ci-dessus.
Prenons l'exemple suivant.
Exemple 14. Comportement cascade sur une relation
@OneToOne
@Entity public class Commune implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE}) private Maire maire ; // reste de la classe }
cascade, disponible sur les annotations :
@OneToOne,
@OneToMany et
@ManyToMany. La valeur de cet attribut est une énumération de type
CascadeType. En plus des valeurs
DETACH,
MERGE,
PERSIST,
REMOVE,
REFRESH, cette énumération définit la valeur
ALL, qui correspond à toutes les valeurs à la fois.
Remarquons bien que l'annotation
@ManyToOne ne définit pas cet attribut.
remove() de l'
entity manager
.
Dans l'exemple de nos communes et de nos maires, effacer une commune entraînera l'effacement du maire. Mais l'appel à
commune.setMaire(null) doit aussi entraîner l'effacement de ce maire, dans la mesure où cette entité sera orpheline.
On dispose pour cela d'un attribut défini sur
@OneToOne et
@OneToMany :
orphanRemoval. Le fait de mettre cet attribut à
true activera la détection d'entités orphelines, et leur effacement automatique.
Exemple 15. Effacement des orphelins sur une relation
@OneToOne
@Entity public class Commune implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE} orphanRemoval=true) private Maire maire ; // reste de la classe }