🖨️ Version PDF
Nous allons mettre en place la partie de gestion des Exceptions pour l’Adhérent (propriétaire). Elle pourra vous servir pour les autres entités et contrôleurs.
Reprenons le code de base de notre contrôleur : AdherentController
@RestController @RequestMapping("/api/adherents") public class AdherentController { private final AdherentService adherentService; public AdherentController(AdherentService adherentService) { this.adherentService = adherentService; } // Récupérer tous les adhérents @GetMapping public ResponseEntity<List<AdherentDto>> getAllAdherents() { List<AdherentDto> adherents = adherentService.getAllAdherents(); return ResponseEntity.ok(adherents); } // Créer un adhérent @PostMapping public ResponseEntity<AdherentDto> createAdherent(@Valid @RequestBody AdherentCreateDto createDto) { AdherentDto savedAdherent = adherentService.createAdherent(createDto); return ResponseEntity.ok(savedAdherent); } // Récupérer un adhérent par ID @GetMapping("/{id}") public ResponseEntity<AdherentDto> getAdherent(@PathVariable Long id) { AdherentDto adherentDto = adherentService.getAdherentById(id); return ResponseEntity.ok(adherentDto); } // Mettre à jour un adhérent @PutMapping("/{id}") public ResponseEntity<AdherentDto> updateAdherent( @PathVariable Long id, @Valid @RequestBody AdherentUpdateDto updateDto) { AdherentDto updatedAdherent = adherentService.updateAdherent(id, updateDto); return ResponseEntity.ok(updatedAdherent); } }
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.NOT_FOUND) // Renvoie automatiquement un statut 404 public class AdherentNotFoundException extends RuntimeException { public AdherentNotFoundException(Long id) { super("Adhérent non trouvé avec l'ID : " + id); } public AdherentNotFoundException(String email) { super("Adhérent non trouvé avec l'email : " + email); } }
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.CONFLICT) // Renvoie automatiquement un statut 409 public class AdherentAlreadyExistsException extends RuntimeException { public AdherentAlreadyExistsException(String email) { super("Un adhérent avec l'email " + email + " existe déjà."); } }
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.BAD_REQUEST) // Renvoie automatiquement un statut 400 public class AdherentInvalidException extends RuntimeException { public AdherentInvalidException(String message) { super(message); } }
Cette classe permet de standardiser le format des réponses d’erreur renvoyées par l’API.
import java.time.LocalDateTime; import java.util.List; public record ErrorResponse( int status, String error, String message, Object details, // Peut être une liste d'erreurs ou null LocalDateTime timestamp ) { // Constructeur pour les erreurs simples (sans détails) public ErrorResponse(int status, String error, String message, LocalDateTime timestamp) { this(status, error, message, null, timestamp); } // Constructeur pour les erreurs avec détails (ex : liste de validations) public ErrorResponse(int status, String error, String message, Object details, LocalDateTime timestamp) { this.status = status; this.error = error; this.message = message; this.details = details; this.timestamp = timestamp; } }
Ce gestionnaire centralise la gestion des exceptions pour toute l’application.
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.WebRequest; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @RestControllerAdvice // Intercepte les exceptions dans tous les contrôleurs public class GlobalExceptionHandler { // Gestion des erreurs de validation (ex : @Valid échoue) @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex, WebRequest request) { List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.toList()); ErrorResponse errorResponse = new ErrorResponse( HttpStatus.BAD_REQUEST.value(), "Validation échouée", "Erreurs de validation dans la requête", errors, LocalDateTime.now() ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } // Gestion des adhérents non trouvés @ExceptionHandler(AdherentNotFoundException.class) public ResponseEntity<ErrorResponse> handleAdherentNotFound(AdherentNotFoundException ex, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse( HttpStatus.NOT_FOUND.value(), "Ressource non trouvée", ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } // Gestion des adhérents déjà existants @ExceptionHandler(AdherentAlreadyExistsException.class) public ResponseEntity<ErrorResponse> handleAdherentAlreadyExists(AdherentAlreadyExistsException ex, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse( HttpStatus.CONFLICT.value(), "Conflit", ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT); } // Gestion des données d'adhérent invalides @ExceptionHandler(AdherentInvalidException.class) public ResponseEntity<ErrorResponse> handleAdherentInvalid(AdherentInvalidException ex, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse( HttpStatus.BAD_REQUEST.value(), "Données invalides", ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } // Gestion des exceptions génériques (pour éviter les erreurs 500 non gérées) @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse( HttpStatus.INTERNAL_SERVER_ERROR.value(), "Erreur interne", ex.getMessage(), LocalDateTime.now() ); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } }
Voici comment utiliser ces exceptions dans le service pour les adhérents.
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Service @Transactional public class AdherentService { private final AdherentRepository adherentRepository; public AdherentService(AdherentRepository adherentRepository) { this.adherentRepository = adherentRepository; } // Créer un adhérent public AdherentDto createAdherent(AdherentCreateDto createDto) { // Vérifie si un adhérent avec le même email existe déjà if (adherentRepository.existsByEmail(createDto.email())) { throw new AdherentAlreadyExistsException(createDto.email()); } Adherent adherent = new Adherent(); adherent.setNom(createDto.nom()); adherent.setAdresse(createDto.adresse()); adherent.setTelephone(createDto.telephone()); adherent.setEmail(createDto.email()); Adherent savedAdherent = adherentRepository.save(adherent); return mapToDto(savedAdherent); } // Récupérer un adhérent par ID public AdherentDto getAdherentById(Long id) { Adherent adherent = adherentRepository.findById(id).orElseThrow(() -> new AdherentNotFoundException(id)); return mapToDto(adherent); } // Récupérer un adhérent par email public AdherentDto getAdherentByEmail(String email) { Adherent adherent = adherentRepository.findByEmail(email).orElseThrow(() -> new AdherentNotFoundException(email)); return mapToDto(adherent); } // Mettre à jour un adhérent public AdherentDto updateAdherent(Long id, AdherentUpdateDto updateDto) { Adherent adherent = adherentRepository.findById(id).orElseThrow(() -> new AdherentNotFoundException(id)); // Vérifie si un autre adhérent utilise déjà le nouvel email if (updateDto.email() != null && !updateDto.email().equals(adherent.getEmail()) && adherentRepository.existsByEmail(updateDto.email())) { throw new AdherentAlreadyExistsException(updateDto.email()); } // Met à jour les champs fournis if (updateDto.nom() != null) { adherent.setNom(updateDto.nom()); } if (updateDto.adresse() != null) { adherent.setAdresse(updateDto.adresse()); } if (updateDto.telephone() != null) { adherent.setTelephone(updateDto.telephone()); } if (updateDto.email() != null) { adherent.setEmail(updateDto.email()); } Adherent updatedAdherent = adherentRepository.save(adherent); return mapToDto(updatedAdherent); } // Supprimer un adhérent public void deleteAdherent(Long id) { if (!adherentRepository.existsById(id)) { throw new AdherentNotFoundException(id); } adherentRepository.deleteById(id); } // Méthode utilitaire pour convertir Adherent en AdherentDto private AdherentDto mapToDto(Adherent adherent) { return new AdherentDto( adherent.getId(), adherent.getNom(), adherent.getAdresse(), adherent.getTelephone(), adherent.getEmail() ); } }
Voici comment le contrôleur peut utiliser ces exceptions et le service.
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; @RestController @RequestMapping("/api/adherents") public class AdherentController { private final AdherentService adherentService; public AdherentController(AdherentService adherentService) { this.adherentService = adherentService; } // Créer un adhérent @PostMapping public ResponseEntity<AdherentDto> createAdherent(@Valid @RequestBody AdherentCreateDto createDto) { AdherentDto savedAdherent = adherentService.createAdherent(createDto); return ResponseEntity.status(HttpStatus.CREATED).body(savedAdherent); } // Récupérer un adhérent par ID @GetMapping("/{id}") public ResponseEntity<AdherentDto> getAdherent(@PathVariable Long id) { AdherentDto adherentDto = adherentService.getAdherentById(id); return ResponseEntity.ok(adherentDto); } // Récupérer un adhérent par email @GetMapping("/email/{email}") public ResponseEntity<AdherentDto> getAdherentByEmail(@PathVariable String email) { AdherentDto adherentDto = adherentService.getAdherentByEmail(email); return ResponseEntity.ok(adherentDto); } // Mettre à jour un adhérent @PutMapping("/{id}") public ResponseEntity<AdherentDto> updateAdherent( @PathVariable Long id, @Valid @RequestBody AdherentUpdateDto updateDto) { AdherentDto updatedAdherent = adherentService.updateAdherent(id, updateDto); return ResponseEntity.ok(updatedAdherent); } // Supprimer un adhérent @DeleteMapping("/{id}") public ResponseEntity<Void> deleteAdherent(@PathVariable Long id) { adherentService.deleteAdherent(id); return ResponseEntity.noContent().build(); } }
Voici à quoi ressembleront les réponses d’erreur pour les différentes exceptions :
{ "status": 404, "error": "Ressource non trouvée", "message": "Adhérent non trouvé avec l'ID : 1", "timestamp": "2024-01-10T14:30:00" }
{ "status": 409, "error": "Conflit", "message": "Un adhérent avec l'email philippe@example.com existe déjà.", "timestamp": "2024-01-10T14:30:00" }
{ "status": 400, "error": "Validation échouée", "message": "Erreurs de validation dans la requête", "details": [ "nom: Le nom ne peut pas être vide", "email: L'email doit être valide" ], "timestamp": "2024-01-10T14:30:00" }
Exceptions personnalisées :
Gestion centralisée :
Validation :
Clarté et maintenabilité :