@OneToOne
est un luxe.
JPA propose pour cela une fonctionnalité, qui permet d'inclure les champs de l'objet en relation avec ceux de l'objet maître. Cette technique résout tous les problèmes.
@Embeddable
plutôt qu'
@Entity
. Les contraintes sur ces classes sont les mêmes : elles doivent être sérializables, et comporter un constructeur vide, par défaut ou explicite.
Une entité incluse n'a pas besoin de déclarer de clé primaire : elle n'a pas de table en propre, et partagera donc la même clé primaire que les objets qui la déclarent en relation.
Voyons un exemple d'objet inclus.
Exemple 17. Déclaration d'objets inclus
@Embeddable public class Adresse implements Serializable { @Column(length=40) private String rue ; @ManyToOne private Commune commune ; // reste de la classe }
@Entity
, il ne s'agit donc pas d'une entité JPA. Elle est annotée avec
@Embeddable
, et ne pourra être utilisée que dans des relations annotées avec
@Embedded
.
Cette classe va nous permettre d'ajouter une adresse à nos marins. On remarque que l'objet inclus est lui-même en relation avec les communes, ce qui est autorisé.
Exemple 18. Utilisation d'objets inclus
@Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String name ; @Embedded private Adresse adresse ; // reste de la classe }
adresse
avec
@OneToOne
, comme on l'aurait fait pour une relation classique, nous l'avons annotée avec
@Embedded
, ce qui signifie que les champs de notre adresse seront créés directement dans la table
Marin
. Un objet référencé dans une relation annotée par
@Embedded
doit nécessairement être annoté par
@Embeddable
.
rue
et la colonne de jointure vers la table
Commune
.
marin
, qui possède une adresse dont tous les champs sont vides.
On peut mettre en place plusieurs solutions pour régler ce problème :
adresse
particulier, dont les champs indiquent que cet objet correspond à un objet nul. Cette approche n'est valide que dans certains cas, dans d'autres, une telle configuration de champs n'existe pas.
null
quand le champ inclus est en fait nul. Voyons ceci sur un exemple.
On notera deux choses sur cet exemple :
isNull
de la classe
Adresse
est positionné à
true
par défaut. Donc, toute relation vers une adresse initialisée avec une adresse vide génèrera un retour
null
sur appel du
getter
de cette relation.
Marin
, le champ
adresse
est initialisé sur une valeur particulière :
Adresse.ADRESSE_NULL
, de façon à ne jamais être nul. Cela permet d'éviter les éventuelles erreurs si cette relation n'a pas été initialisée. Notons que l'objet
Adresse.ADRESSE_NULL
génère bien un retour nul du
getter
associé.
Exemple 19. Champ inclus nul
// // Entité Adresse // @Embeddable public class Adresse implements Serializable { private boolean isNull = true ; private String rue ; @ManyToOne private Commune commune ; // objet adresse utilisé pour l'initialisation // le champ isNull de cet objet est true public static ADRESSE_NULL = new Adresse() ; // méthode appelée par les getters public static Adresse getAdresse(Adresse adresse) { if (adresse == null || adresse.isNull) { return null ; } else { return adresse ; } } // reste de la classe } // // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String name ; // le champ est initialisé non nul, afin d'éviter // les erreurs à l'écriture @Embedded private Adresse adresse = Adresse.ADRESSE_NULL ; // Getter pour l'adresse public Adresse getAdresse() { return Adresse.getAdresse(this.adresse) ; } // reste de la classe }
Exemple 20. Renommage des colonnes des objets inclus
@Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String name ; @Embedded @AttributeOverrides({ @AttributeOverride( name="isNull", column=@Column(name="adr_courante_isnull")), @AttributeOverride( name="rue", column=@Column(name="adr_courante_rue")) }) @AssociationOverrides({ @AssociationOverride( name="commune", joinColumns=@JoinColumn(name="adr_courante_commune_id")) }) private Adresse adresseCourante ; @Embedded @AttributeOverrides({ @AttributeOverride( name="isNull", column=@Column(name="adr_naissance_courante_isnull")), @AttributeOverride( name="rue", column=@Column(name="adr_naissance_courante_rue")) }) @AssociationOverrides({ @AssociationOverride( name="commune", joinColumns=@JoinColumn(name="adr_naissance_commune_id")) }) private Adresse adresseDeNaissance ; // reste de la classe }
Adresse
porte deux types de champs : des types de base et des relations. Les types de base sont stockés dans des colonnes, et les relations dans des colonnes de jointure, qui peuvent être rangées dans la même table qu'une autre entité, ou dans une table propre (table de jointure).
On renomme une colonne par l'annotation
@AttributOverride
. Cette annotation prend deux attributs :
name
, de type
String
: désigne le nom du champ dans la classe ;
column
, de type
@Column
: définit la colonne dans laquelle ce champ sera enregistré. Le type
@Column
définit lui-même plusieurs attributs, dont
name
que nous utilisons ici.
Adresse
porte deux champs :
isNull
et
rue
, nous devons utiliser deux annotations
@AttributeOverride
. Ces deux annotations sont regroupées dans une annotation
@AttributeOverrides
(notons le "s" à la fin), placée sur le champ inclus.
Reste la relation que
Adresse
porte, vers
Commune
. Cette relation est ici enregistrée dans une colonne de jointure, clé étrangère qui référence la clé primaire de la table
Commune
.
On redéfinit le nom de cette colonne en utilisant une annotation
@AssociationOverride
, qui fonctionne de la même façon que
@AttributeOverride
. Elle définit trois attributs :
name
, de type
String
: désigne le nom du champ dans la classe. Cet attribut est le même que dans
@AttributeOverride
.
joinColumns
, de type tableau de
@JoinColumn
. Effectivement, JPA supporte les clés primaires composites (ce point n'est pas traité ici), et dans ce cas une jointure peut être enregistrée dans plusieurs colonnes. Ici ce tableau ne porte qu'une unique valeur, de type
@JoinColumn
.
joinTable
: comme on l'a vu, la jointure peut être enregistrée dans une table de jointure. Dans ce cas, cet attribut nous donne le nom de cette table.
@JoinColumn
fonctionne comme l'annotation
@Column
, et définit des attributs équivalents.
On obtient alors le schéma suivant.
@Embeddable
. Une telle collection doit être annotée
@ElementCollection
, comme une collection dont les éléments sont des types de base. Les objets inclus seront alors enregistrés dans une table à part, de la même manière que les types de base.