Ressources
La première relation que nous allons découvrir avec la 1:1. Elle consiste à assurer la correspondance entre deux entités de manière exclusive, où chaque instance d’une entité est directement liée à une unique instance de l’autre entité.
- Une personne à un unique passeport
- Un passeport appartient à une seule personne
Plusieurs solutions permettent de résoudre ce problème, l’étude sémitique de chaque d’entre elle nous permettra :
- de comprendre les compromis à faire
- comprendre l’utilité de la propriété
mappedBy - et finalement l’utilisation du
CASCADE
x1 OneToOne = Unidirectionnelle
Premièrement nous pouvons écrire le bout de code permettant d’avoir une relation unidirectionnelle entres nos deux classes.
@Entity
public class Personne {
@OneToOne
private Passeport passeport;
}Voilà.
Maintenant si on souhaite aller plus loin et pouvoir naviguer dans les deux directions plusieurs options s’offrent à nous
- avoir deux relations unidirectionnelles
- ou avoir une relation bidirectionnelle
Code non executable
Avant de rentrer les des solutions pour naviguer dans les deux sens, une petit aparté. On pourra penser qu’écrire le code suivant serait suffisant :
- préciser la relation
@OneToOneuniquement dans la classePersonne
@Entity
public class Personne {
@OneToOne private Passeport passeport;
}
@Entity
public class Passeport {
private Person person; // aucune annotation
}Néanmoins ce code conduit à l’exception JdbcTypeRecommendationException, en effet :
Passwordest annoté de@Entity- et fait référence à
Personneaussi une entité et sans annotation JPA pour définir le type de la relation
Alors on pourrait tout simplement ne pas préciser l’attribut Person person dans la classe Passeport mais dans ce cas nous n’aurons pas de relation bidirectionnelle entre les deux classes.
x2 OneToOne = x2 unidirectionnelles
Ainsi si nous souhaitons conserver la navigation dans les deux sens nous devons conserver l’attribut. Une solution consiste d’y rajouter l’annotation @OneToOne également
@Entity
public class Personne {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nom;
@OneToOne
private Passeport passeport;
}@Entity
public class Passeport {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String numero;
@OneToOne
private Personne personne;
}Résultat
En précisant deux fois l’annotation OneToOne nous avons avons deux Fk (une dans chaque table).
- On parle de deux relations unidirectionnelles et non d’une relation bidirectionnelle
Personne
| id | nom | passeport_id |
| --- | -------- | ------------ |
| 1 | John Doe | 101 |
Passeport
| id | numero | personne_id |
| --- | --------- | ----------- |
| 101 | A12345678 | 1 |
Mais dans la plupart des cas, nous n’avons pas besoin de clés étrangères dans les deux tables pour une relation univoque.
OneToOne + MappedBy = bidirectionnelle
Notre objectif est donc d’avoir la représentation suivante
| id | nom |
| --- | -------- |
| 1 | John Doe |
| id | numero | personne_id |
| --- | --------- | ----------- |
| 101 | A12345678 | 1 |
Pour ce faire, nous devons utiliser la propriété mappedBy, il permet d’indiquer :
- Le inverse side de la relation, c’est-à-dire le côté qui contient l’attribut mappedBy (nous
Personne) - Le owning side de la relation, c’est-à-dire le côté de la relation qui onws la clé étrangère (nous
Password)
La valeur à donner à mappedBy est le côté inverse de la relation, celui qui ne contient pas la FK
/* (Inverse Side) */
@Entity
public class Personne {
@OneToOne(mappedBy = "personne") // Inverse side, la FK n'est pas dans Personne
private Passeport passeport;
}/* (Owning Side) */
@Entity
public class Passeport {
@OneToOne // Foreign key will be here
// @JoinColumn(name = "fk_personne_id) nom de colonne explicite
private Personne personne;
}Résultat
Nous obtenons bien de résultat souhaité
| id | nom |
| --- | -------- |
| 1 | John Doe |
| id | numero | personne_id |
| --- | --------- | ----------- |
| 101 | A12345678 | 1 |
Code
Ci-dessous le code pour arriver à ce résultat
entityManager.getTransaction().begin();
Passeport passeport = new Passeport();
passeport.setNumero("A12345678");
// Create Personne entity and associate it with the Passeport
Personne personne = new Person();
personne.setNom("John Doe");
personne.setPasseport(passeport);
// Persist both entities
entityManager.persist(passeport);
entityManager.persist(personne);
entityManager.getTransaction().commit();Nous remarquons que avons du persister nos deux entités.
entityManager.persist(passeport);
entityManager.persist(personne);Il serait intéressant de persister automatiquement l’entité Passeport lorsqu’on persiste l’entité Personne
OneToOne + MappedBy + Cascade
Ainsi la dernière amélioration consiste à rajouter la propriété CASCADE
@Entity
public class Personne {
@OneToOne(mappedBy = "personne", cascade = CascadeType.ALL)
private Passeport passeport;
}@Entity // aucun changement pour Passeport
public class Passeport {
@OneToOne
private Personne personne;
}En persistant uniquement la personne, le passeport s’est automatiquement vu persisté également
entityManager.getTransaction().begin();
// Crée un passeport
Passeport passeport = new Passeport();
passeport.setNumero("A12345678");
// Crée une personne et associe le passeport
Personne personne = new Personne();
personne.setNom("John Doe");
personne.setPasseport(passeport); // Cascade permettra de persister le passeport
// Persiste uniquement la personne (le passeport sera persisté automatiquement)
entityManager.persist(personne);
entityManager.getTransaction().commit();