Spring Data JPA nous permet de créer des relations rapides et efficaces entre différentes entités. Pour les exemples qui suivent j'utiliserai les classes Chat et Personne tel que décrites ci-dessous.
Relation un à plusieurs (One To Many)
L'exemple suivant démontre la situation dans laquelle une personne peut avoir plusieurs chats, mais chaque chat a un seul propriétaire.
erDiagram
PERSONNE ||--o{ CHAT : "Possède"
- On utilisera les décorateurs
@OneToManyet@ManyToOnepour décrire le lien entre les entités. - Pour lire les décorateurs toujours se mettre dans la peau de l'entité courante et lire de gauche à droite.
-
Une personne a plusieurs chats devient
@OneToMany- One Personne Has Many Chats
-
Dans la classe Chat on ajoute
-
Un chat peut être "possédé" par une seule personne, on conserve donc la clé étrangère dans cette classe (le
@JoinColumnnomme la nouvelle colonne dans la BD) -
Dans la classe Personne on ajoute
-
Une Personne a plusieurs chats et sera liée par la propriété
personnedans un chat. -
Ainsi, dans le code, on pourra appeler
chat.personnepour obtenir les informations du propriétaire du chat. -
On pourra également accéder à la liste des chats d'une personne en utilisant
personne.chats
Relation un à un (OneToOne)
L'exemple suivant démontre la situation dans laquelle une personne peut avoir un seul chat et chaque chat a un seul propriétaire.
erDiagram
PERSONNE ||--o| CHAT : "Possède"
- Semblable à la relation un à plusieurs.
-
On doit décider qui "possède" la clé étrangère. Dans ce cas-ci, je la laisse dans l'entité Chat.
-
Dans la classe Chat on ajoute
-
Un chat peut être "possédé" par une seule personne et je choisis de garder la clé étrangère dans cette entité d'où le
@JoinColumn -
Dans la classe Personne on ajoute
-
On sait quel chat appartient à quelle personne grâce à la propriété
personnede la classeChat
Relation plusieurs à plusieurs (ManyToMany)
L'exemple suivant démontre la situation dans laquelle une personne peut avoir plusieurs chats et chaque chat peut avoir plusieurs propriétaires.
erDiagram
PERSONNE }|--|{ CHAT : "Adopte"
Évidemment, on devra créer une table pour la jointure
erDiagram
PERSONNE ||--|{ ADOPTION_CHAT : "Adopte"
CHAT ||--|{ ADOPTION_CHAT : "Est adopté"
JPA nous permet de faire la création de la table automatiquement avec la bonne configuration.
- Encore une fois on doit décider dans quelle classe on crée le gros de la relation. Je le ferai dans
Chatpour conserver le même format. - On ajoute le décorateur
@ManyToManysur les deux classes - Dans la classe
Chaton remplacera@JoinColumnpar@JoinTable joinColumns = @JoinColumn(name = "chat_id")définit le nom de la colonne pour représenter la clé primaire de la classe dans laquelle on crée la table de jointureinverseJoinColumns = @JoinColumn(name = "personne_id")définit le nom de la colonne pour représenter la clé primaire de la classe liée à celle-ci@UniqueConstraint()permet de s'assurer qu'on ne combine pas plusieurs fois le même chat et propriétaire.-
Dans l'exemple ci-dessus, la table
adoption_chatsera crée et comportera 2 attributs, soit leidd'un chat (identifié par la colonnechat_id) et leidd'une personne (identifié par la colonnepersonne_id). -
Dans la classe personne on ajoute le décorateur
@ManyToMany - On lui indique par quelle propriété de la classe
Chatelle doit faire le lien (dans ce cas-ci l'attributpersonnes)
Relation plusieurs à plusieurs complexe
Pour les relations plusieurs à plusieurs dans lesquelles on voudra conserver plus d'informations que les clés primaires des entités en relation, il faudra nous même créer une nouvelle entité pour en faire la représentation.
Continuons avec l'exemple précédent de relation plusieurs à plusieurs, mais on voudra également garder le moment ou l'adoption d'un chat a lieu.
- Nous devrons créer une entité
AdoptionChat. - Il faudra créer une nouvelle classe pour représenter la clé primaire de cette entité. Celle-ci regroupera les clés primaires des deux identités qu'on veut lier.
-
La classe représentant la clé primaire doit être marquée
@Embeddableet implémenter l'interfaceSerializableainsi que les méthodesEqualsetHashCode(on tire avantage de Lombok pour cela) -
Ensuite on crée notre nouvelle entité qui utilisera cette classe com type pour sa clé primaire.
- On établira également des relations
@ManyToOneavec les entités liées (dans le cas présent une relation avecChatetPersonne)
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Entity
public class AdoptionChat {
@EmbeddedId
private AdoptionKey id;
// Relation avec Personne, @MapsId correspond à l'attribut de la clé primaire
@ManyToOne
@MapsId("personneId")
@JoinColumn(name = "personne_id")
private Personne personne;
// Relation avec Chat, @MapsId correspond à l'attribut de la clé primaire
@ManyToOne
@MapsId("chatId")
@JoinColumn(name = "chat_id")
private Chat chat;
@Column(name = "date_adoption")
private Date dateAdoption;
}
- Chacune des classe aura une relation
@OneToManycorrespondante
// Classe Chat
@OneToMany(mappedBy = "chat")
Set<AdoptionChat> adoptionsChat;
// Classe Personne
@OneToMany(mappedBy = "personne")
Set<AdoptionChat> adoptionsChat;
Assigner un objet dans une relation
-
Dans les services, marquer les méthodes qui modifient la base de données (ajouter, modifier, supprimer) avec le décorateur
@Transactional. Ceci assure l'intégrité des données en cas d'échec de la méthode. -
Même si la base de données conserve seulement la clé étrangère, on voudra manipuler les objets complets dans le code.
-
Par exemple, pour ajouter un propriétaire à un chat
// Extrait de ChatService.java
@Transactional
public void updateChat(Chat chat) {
// Si aucun propriétaire n'est sélectionné
if (chat.getPersonne().getId() == null) {
chat.setPersonne(null);
}
else{
// Récupérer l'objet Personne de la base de données
Personne proprio = personneService.findById(chat.getPersonne().getId());
// L'associer au Chat
chat.setPersonne(proprio);
// Sauvegarder dans la base de données
chatRepository.save(chat);
}
}
-
Il faudra gérer les erreurs potentielles dans le service également, par exemple le non respect des contraintes. On veut éviter de tenter une transaction à la base de données si on sait déjà que la transaction risque de ne pas réussir.
-
Exemple avec une relation un à un. Si la personne à laquelle on essaie d'assigner un chat en possède déjà un, on doit éviter de l'assigner.
@Transactional
public boolean updateChat(Chat chat) {
if (chat.getPersonne().getId() == null) {
chat.setPersonne(null);
chatRepository.save(chat);
return true;
}
else{
Chat chatExistant = chatRepository.findChatByPersonneId(chat.getPersonne().getId());
// Si aucun chat n'est trouvé, le propriétaire peut adopter
if (chatExistant == null) {
Personne proprio = personneService.findById(chat.getPersonne().getId());
chat.setPersonne(proprio);
chatRepository.save(chat);
return true;
}
else {
return false;
}
}
}