Les jointures avec Spring
Introduction
Spring Data JPA est une bibliothèque qui fournit des moyens pour réduire la quantité de code nécessaire pour implémenter la couche de persistance de données dans une application Spring. Elle fournit des interfaces pour les opérations CRUD (Create, Read, Update, Delete) et permet également d’écrire des requêtes personnalisées.
Dans cet exemple, nous avons deux entités : User et Role. Chaque utilisateur a un rôle, ce qui est représenté par une relation un-à-un (@OneToOne) entre User et Role.

un "user" est autorisé par un "role" et un seul
un "role" peut autoriser 0 ou plusieurs "user"
1,1 = 1
0,N = N
L'association résultante = 1,N
Les entités
L’entité User
Le code est à vérifier !
/**
* @Entity : Cette annotation indique que la classe est une entité. C’est une classe légère dont les instances peuvent être stockées dans une base de données.
@Id : Cette annotation spécifie la clé primaire d’une entité. La clé primaire doit être unique et non nulle.
@GeneratedValue : Cette annotation fournit la stratégie de génération automatique des valeurs primaires.
GenerationType.AUTO signifie que le fournisseur de persistance doit sélectionner une stratégie appropriée pour le fournisseur de base de données spécifique.
@OneToMany : Cette annotation définit une association à un à plusieurs à un type d’entité qui doit être persisté.
Dans ce cas, il y a plusieurs utilisateurs qui peuvent être associés à un seul rôle.
@JoinColumn : Cette annotation est utilisée pour spécifier les détails d’une colonne pour joindre les tables d’entités physiques.
Ici, elle spécifie que la colonne id_type dans la table User fait référence à la colonne idType dans la table Role.
private Role role = new Role(0, ""); : Ceci est une référence à l’entité Role. Chaque instance de User est associée à une instance de Role.
Les méthodes publiques comme getId(), setId(Integer id), etc., sont des getters et des setters qui sont utilisés pour accéder et définir les valeurs des variables d’instance privées.
*/
package com.example.demo.Model; // Définition du package
import jakarta.persistence.Entity; // Importation de l'annotation Entity
import jakarta.persistence.GeneratedValue; // Importation de l'annotation GeneratedValue
import jakarta.persistence.GenerationType; // Importation de l'enum GenerationType
import jakarta.persistence.Id; // Importation de l'annotation Id
import jakarta.persistence.OneToMany;
@Entity // Cette classe est une entité, elle peut être persistée dans la base de données
public class User {
@Id // Cette variable est la clé primaire de l'entité
@GeneratedValue(strategy=GenerationType.AUTO) // La valeur de cette variable est générée automatiquement
private Integer id;
private String name; // Nom de l'utilisateur
private String email; // Email de l'utilisateur
private String password; // Ajout du champ password
@ManyToOne
@JoinColumn(name = "role_id")
private Role role;
public Integer getId() { // Getter pour id
return id;
}
public void setId(Integer id) { // Setter pour id
this.id = id;
}
public String getName() { // Getter pour name
return name;
}
public void setName(String name) { // Setter pour name
this.name = name;
}
public String getEmail() { // Getter pour email
return email;
}
public void setEmail(String email) { // Setter pour email
this.email = email;
}
public String getPassword() { // Getter pour password
return password;
}
public void setPassword(String password) { // Setter pour password
this.password = password;
}
public void setRole(Role role) { // Setter pour role
this.role = role;
}
}
Dans cette classe, l’annotation @Entity indique que cette classe est une entité et qu’elle doit être persistée dans la base de données. L’annotation @Id est utilisée pour définir la clé primaire de l’entité et @GeneratedValue est utilisée pour spécifier la stratégie de génération de la clé primaire.
L’annotation @ManyToOne est utilisée pour indiquer qu’il y a une relation plusieurs-à-un entre l’entité User et l’entité Role. L’annotation @JoinColumn est utilisée pour indiquer la colonne qui est utilisée pour joindre les deux tables.
L’entité Role
/**
* @Entity : Cette annotation indique que la classe est une entité. C’est une classe légère dont les instances peuvent être stockées dans une base de données.
* @Id : Cette annotation spécifie la clé primaire d’une entité. La clé primaire doit être unique et non nulle.
* @GeneratedValue : Cette annotation fournit la stratégie de génération automatique des valeurs primaires.
* GenerationType.AUTO signifie que le fournisseur de persistance doit sélectionner une stratégie appropriée pour le fournisseur de base de données spécifique.
* @ManyToOne : Cette annotation définit une association à plusieurs valeurs à un type d’entité qui doit être persisté.
* Dans ce cas, il y a plusieurs rôles qui peuvent être associés à un seul utilisateur.
* private User user; : Ceci est une référence à l’entité User. Chaque instance de Role est associée à une instance de User.
* Les méthodes publiques comme getIdType(), setIdType(Integer idType), etc.,
* sont des getters et des setters qui sont utilisés pour accéder et définir les valeurs des variables d’instance privées.
*/
package com.example.demo.Model;
import jakarta.persistence.Entity; // Importation de l'annotation Entity
import jakarta.persistence.GeneratedValue; // Importation de l'annotation GeneratedValue
import jakarta.persistence.GenerationType; // Importation de l'enum GenerationType
import jakarta.persistence.Id; // Importation de l'annotation Id
import jakarta.persistence.ManyToOne; // Importation de l'annotation ManyToOne
@Entity // Cette classe est une entité, elle peut être persistée dans la base de données
public class Role {
@Id // Cette variable est la clé primaire de l'entité
@GeneratedValue(strategy=GenerationType.AUTO) // La valeur de cette variable est générée automatiquement
private Integer idType;
private String typeName; // Nom du type de rôle
@OneToMany(mappedBy = "role")
private List<User> users;
public Role() {} // Constructeur par défaut
public Role(Integer idType, String typeName) { // Constructeur avec paramètres
this.idType = idType;
this.typeName = typeName;
}
// Getters et setters...
public Integer getIdType() { // Getter pour idType
return idType;
}
public void setIdType(Integer idType) { // Setter pour idType
this.idType = idType;
}
public String getTypeName() { // Getter pour typeName
return typeName;
}
public void setTypeName(String typeName) { // Setter pour typeName
this.typeName = typeName;
}
}
Cette classe est similaire à la classe User, mais elle représente le rôle d’un utilisateur.
Ce code définit une classe Role qui représente un rôle dans une application. La classe est annotée avec @Entity, ce qui indique qu’elle peut être persistée dans une base de données. La variable idType est annotée avec @Id et @GeneratedValue, ce qui indique qu’elle est la clé primaire de l’entité et que sa valeur sera générée automatiquement. La classe contient également une variable typeName qui représente le nom du type de rôle, ainsi que des getters et des setters pour accéder et modifier les valeurs des variables d’instance.
Les repositories
Les repositories sont des interfaces qui fournissent des méthodes pour effectuer des opérations sur les données. Dans cet exemple, nous avons deux repositories : UserRepository et RoleRepository.
UserRepository
/**
* Ce code définit une interface UserRepository qui étend CrudRepository.
* CrudRepository est une interface Spring Data qui fournit des méthodes génériques pour l’accès aux données CRUD (Create, Read, Update, Delete).
* En étendant CrudRepository, UserRepository hérite de ces méthodes CRUD génériques pour l’entité User.
* Cela signifie que vous pouvez utiliser ces méthodes pour créer, lire, mettre à jour et supprimer des utilisateurs dans votre base de données sans avoir à implémenter ces méthodes vous-même.
*
* De plus, vous déclarez deux méthodes personnalisées findByName(String name) et findById(int id).
* Spring Data JPA générera automatiquement une implémentation de ces méthodes à l’exécution.
* Ces méthodes vous permettront de trouver des utilisateurs dans votre base de données en fonction de leur nom ou de leur ID.
* Vous n’avez pas besoin d’écrire le code SQL ou JPQL correspondant ; Spring Data JPA s’en occupe pour vous.
*/
package com.example.demo.Repository; // Définition du package
import java.util.List; // Importation de la classe List
import org.springframework.data.repository.CrudRepository; // Importation de l'interface CrudRepository
import com.example.demo.Model.User; // Importation de la classe User
public interface UserRepository extends CrudRepository<User, Integer> {
// Cette interface étend CrudRepository qui est une interface Spring Data pour l'accès générique aux données CRUD
// Elle prend deux paramètres : le type de l'entité et le type de l'ID de l'entité
List<User> findByName(String name);
// Déclaration d'une méthode personnalisée pour trouver des utilisateurs par leur nom
// Spring Data JPA générera automatiquement une implémentation de cette méthode à l'exécution
List<User> findById(int id);
// Déclaration d'une méthode personnalisée pour trouver des utilisateurs par leur ID
// Spring Data JPA générera automatiquement une implémentation de cette méthode à l'exécution
}
Dans cette interface, nous définissons deux méthodes personnalisées : findByName(String name) et findById(int id). Spring Data JPA générera automatiquement une implémentation de ces méthodes à l’exécution.
RoleRepository
/**
* Ce code définit une interface RoleRepository qui étend CrudRepository.
* CrudRepository est une interface Spring Data qui fournit des méthodes génériques pour l’accès aux données CRUD (Create, Read, Update, Delete).
* En étendant CrudRepository, RoleRepository hérite de ces méthodes CRUD génériques pour l’entité Role.
* Cela signifie que vous pouvez utiliser ces méthodes pour créer, lire, mettre à jour et supprimer des rôles dans votre base de données sans avoir à implémenter ces méthodes vous-même.
* De plus, vous déclarez une méthode personnalisée findByTypeName(String typeName).
* Spring Data JPA générera automatiquement une implémentation de cette méthode à l’exécution.
* Cette méthode vous permettra de trouver des rôles dans votre base de données en fonction de leur nom.
* Vous n’avez pas besoin d’écrire le code SQL ou JPQL correspondant ; Spring Data JPA s’en occupe pour vous.
*/
package com.example.demo.Repository; // Définition du package
import java.util.List; // Importation de la classe List
import org.springframework.data.repository.CrudRepository; // Importation de l'interface CrudRepository
import com.example.demo.Model.Role; // Importation de la classe Role
public interface RoleRepository extends CrudRepository<Role, Integer> {
// Cette interface étend CrudRepository qui est une interface Spring Data pour l'accès générique aux données CRUD
// Elle prend deux paramètres : le type de l'entité et le type de l'ID de l'entité
List<Role> findByTypeName(String typeName);
// Déclaration d'une méthode personnalisée pour trouver des rôles par leur nom
// Spring Data JPA générera automatiquement une implémentation de cette méthode à l'exécution
}
Dans cette interface, nous définissons une méthode personnalisée : findByTypeName(String typeName). Comme pour UserRepository, Spring Data JPA générera automatiquement une implémentation de cette méthode à l’exécution.
Les contrôleurs
Les contrôleurs sont des classes qui gèrent les requêtes HTTP entrantes et renvoient des réponses HTTP. Dans cet exemple, nous avons deux contrôleurs : UserController et RoleController.
UserController
/**
* Ce code définit un contrôleur REST pour l’entité Role. Un contrôleur est une classe qui gère les requêtes HTTP entrantes et renvoie des réponses HTTP. Dans ce cas,
* RoleController gère les requêtes HTTP pour les opérations CRUD sur l’entité Role.
* L’annotation @RestController indique que cette classe est un contrôleur REST.
* L’annotation @RequestMapping(path="/roles") spécifie que les URL de ce contrôleur commencent par /roles.
* L’annotation @Autowired est utilisée pour injecter automatiquement l’interface RoleRepository dans ce contrôleur.
* Cela signifie que vous pouvez utiliser toutes les méthodes définies dans RoleRepository sans avoir à créer une instance de RoleRepository vous-même.
* Les annotations @PostMapping, @GetMapping, @PutMapping et @DeleteMapping sont utilisées pour
* mapper les requêtes HTTP POST, GET, PUT et DELETE respectivement sur les méthodes correspondantes.
* Par exemple, @PostMapping(path="/add") mappe les requêtes POST sur /roles/add à la méthode addNewRole.
* Chaque méthode du contrôleur gère une opération CRUD spécifique. Par exemple, la méthode addNewRole crée un nouveau rôle, la méthode getAllRoles renvoie tous les rôles, etc.
* Les méthodes renvoient une réponse HTTP qui peut être une chaîne (par exemple, “Saved”, “Updated”, etc.) ou un objet (par exemple, une liste de rôles).
*/
package com.example.demo.Controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.Model.User;
import com.example.demo.Model.Role;
import com.example.demo.Repository.RoleRepository;
import com.example.demo.Repository.UserRepository;
@Controller // Cette classe est un contrôleur
@RequestMapping(path="/users") // Les URL commencent par /user (après le chemin de l'application)
public class UserController {
@Autowired // Cela signifie obtenir le bean appelé userRepository
// Qui est auto-généré par Spring, nous l'utiliserons pour gérer les données
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
/**
*
* @param name
* @param email
* @param password
* @param roleId
* @return
* ex: curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "name=JohnDoe&email=johndoe@example.com&password=123456&roleId=1" http://localhost:8080/users/add
*/
@PostMapping(path="/add") // Cartographie UNIQUEMENT les requêtes POST
public @ResponseBody String addNewUser (@RequestParam String name
, @RequestParam String email, @RequestParam String password, @RequestParam Integer roleId) {
// @ResponseBody signifie que la chaîne renvoyée est la réponse, pas un nom de vue
// @RequestParam signifie qu'il s'agit d'un paramètre de la requête GET ou POST
User n = new User();
n.setName(name);
n.setEmail(email);
n.setPassword(password); // Définition du password de l'utilisateur
Role role = roleRepository.findById(roleId).orElse(null); // Récupération du rôle à partir de la base de données
if (role != null) {
n.setRole(role); // Association du rôle à l'utilisateur
}
userRepository.save(n);
return "Saved";
}
// curl -X POST -d "name=nom&email=email" http://localhost:8080/users/add
@GetMapping(path="/all")
public @ResponseBody Iterable<User> getAllUsers() {
// Cela renvoie un JSON ou un XML avec les utilisateurs
return userRepository.findAll();
}
// curl -X GET http://localhost:8080/users/all
@GetMapping(path="/findbyid/{id}")
public @ResponseBody List<User> getId(@PathVariable int id ) {
// Cela renvoie un JSON ou un XML avec les utilisateurs qui ont le nom fourni
return userRepository.findById(id);
}
// curl -X GET http://localhost:8080/users/findbyid/id
@GetMapping(path="/findbyname/{name}")
public @ResponseBody List<User> getName(@PathVariable String name) {
// Cela renvoie un JSON ou un XML avec les utilisateurs qui ont le nom fourni
return userRepository.findByName(name);
}
// curl -X GET http://localhost:8080/users/findbyname/nom
@PostMapping(path="/update/{id}")
public @ResponseBody String updateUserName(@PathVariable Integer id, @RequestParam String name) {
// Cela met à jour le nom de l'utilisateur avec l'id fourni
User user = userRepository.findById(id).orElse(null);
if (user != null) {
user.setName(name);
userRepository.save(user);
return "Updated";
} else {
return "User not found";
}
}
// curl -X POST -d "name=nouveauNom" http://localhost:8080/users/update/id
@DeleteMapping(path="/delete/{id}")
public @ResponseBody String deleteUser(@PathVariable Integer id) {
// Cela supprime l'utilisateur avec l'id fourni
User user = userRepository.findById(id).orElse(null);
if (user != null) {
userRepository.delete(user);
return "Deleted";
} else {
return "User not found";
}
}
// curl -X DELETE http://localhost:8080/users/delete/id
@PutMapping(path="/{id}/role")
public @ResponseBody String updateUserRole(@PathVariable Integer id, @RequestParam Integer idType) {
User user = userRepository.findById(id).orElse(null);
Role role = roleRepository.findById(idType).orElse(null);
if (user != null && role != null) {
user.setRole(role);
userRepository.save(user);
return "Updated";
} else {
return "User or Role not found";
}
}
// curl -X PUT -d "idType=idType" http://localhost:8080/users/id/role
// curl -X PUT -d "idType=2" http://localhost:8080/users/1/role
}
RoleController
// Déclaration du package dans lequel se trouve la classe
package com.example.demo.Controller;
// Importation des classes nécessaires à partir de leurs packages respectifs
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import com.example.demo.Model.Role;
import com.example.demo.Repository.RoleRepository;
import java.util.List;
// @RestController indique que cette classe est un contrôleur REST
@RestController
// @RequestMapping spécifie que les URL de ce contrôleur commencent par /roles
@RequestMapping(path="/roles")
public class RoleController {
// @Autowired permet d'injecter automatiquement le dépôt de rôles
@Autowired
private RoleRepository roleRepository;
// @PostMapping mappe les requêtes POST sur /roles/add
// Exemple d'utilisation : curl -X POST -d "typeName=admin" http://localhost:8080/roles/add
@PostMapping(path="/add")
public @ResponseBody String addNewRole (@RequestParam String typeName) {
// Crée une nouvelle instance de Role
Role r = new Role();
// Définit le typeName du rôle
r.setTypeName(typeName);
// Sauvegarde le rôle dans la base de données
roleRepository.save(r);
// Renvoie une réponse indiquant que le rôle a été sauvegardé
return "Saved";
}
// @GetMapping mappe les requêtes GET sur /roles
@GetMapping(path="/all")
public @ResponseBody List<Role> getAllRoles() {
// Renvoie tous les rôles de la base de données
return (List<Role>) roleRepository.findAll();
}
// @GetMapping mappe les requêtes GET sur /roles/{id}
@GetMapping(path="/{id}")
public @ResponseBody Role getRole(@PathVariable Integer id) {
// Renvoie le rôle avec l'ID spécifié, ou null si aucun rôle avec cet ID n'existe
return roleRepository.findById(id).orElse(null);
}
// @PutMapping mappe les requêtes PUT sur /roles/{id}
// Exemple d'utilisation : curl -X PUT -d "typeName=nouveauNom" http://localhost:8080/roles/{id}
@PutMapping(path="/{id}")
public @ResponseBody String updateRole(@PathVariable Integer id, @RequestParam String typeName) {
// Trouve le rôle avec l'ID spécifié
Role role = roleRepository.findById(id).orElse(null);
if (role != null) {
// Si le rôle existe, met à jour son typeName et sauvegarde le rôle mis à jour dans la base de données
role.setTypeName(typeName);
roleRepository.save(role);
return "Updated";
} else {
// Si le rôle n'existe pas, renvoie un message indiquant que le rôle n'a pas été trouvé
return "Role not found";
}
}
// @DeleteMapping mappe les requêtes DELETE sur /roles/{id}
@DeleteMapping(path="/{id}")
public @ResponseBody String deleteRole(@PathVariable Integer id) {
// Trouve le rôle avec l'ID spécifié
Role role = roleRepository.findById(id).orElse(null);
if (role != null) {
// Si le rôle existe, supprime le rôle de la base de données et renvoie un message indiquant que le rôle a été supprimé
roleRepository.delete(role);
return "Deleted";
} else {
// Si le rôle n'existe pas, renvoie un message indiquant que le rôle n'a pas été trouvé
return "Role not found";
}
}
}
Ces contrôleurs gèrent les requêtes HTTP pour les opérations CRUD sur les entités User et Role. Chaque méthode du contrôleur gère une opération CRUD spécifique. Par exemple, la méthode addNewUser dans UserController crée un nouvel utilisateur, la méthode getAllRoles dans RoleController renvoie tous les rôles, etc. Les méthodes renvoient une réponse HTTP qui peut être une chaîne (par exemple, “Saved”, “Updated”, etc.) ou un objet (par exemple, une liste d’utilisateurs ou de rôles).
Les annotations @PostMapping, @GetMapping, @PutMapping et @DeleteMapping sont utilisées pour mapper les requêtes HTTP POST, GET, PUT et DELETE respectivement sur les méthodes correspondantes. Par exemple, @PostMapping(path="/add") dans UserController mappe les requêtes POST sur /users/add à la méthode addNewUser.
L’annotation @Autowired est utilisée pour injecter automatiquement les interfaces UserRepository et RoleRepository dans ces contrôleurs. Cela signifie que vous pouvez utiliser toutes les méthodes définies dans ces repositories sans avoir à créer une instance de ces repositories vous-même.
Conclusion
Avec Spring Data JPA, vous pouvez facilement créer des applications avec une couche de persistance de données robuste sans avoir à écrire beaucoup de code. Les jointures entre les entités peuvent être facilement gérées en utilisant les annotations appropriées. De plus, vous pouvez définir des méthodes personnalisées dans vos repositories pour effectuer des requêtes spécifiques à votre application.