🖨️ Version PDF
Dans une application métier, l’objet principal (ici Chien) concentre :
Chien
Si on code trop vite :
Objectif : construire une feature propre dès le départ comme nous le ferons aussi pour l’adhérent (propriétaire)
`Race 1 ---- * Chien` `Proprietaire 1 ---- * Chien`
Le chien est associé à une race et un propriétaire (ManyToOne).
Pour le moment, je n’utilise pas le plugin Lombok mais on pourrait l’utiliser par la suite pour alléger notre code.
Explications :
@ManyToOne(fetch=LAZY)
@Enumerated(EnumType.STRING)
@Entity public class Chien { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable=false, unique=true) private String numeroTatouage; @Column(nullable=false) private String nom; @Enumerated(EnumType.STRING) @Column(nullable=false) private EtatChien etat; @ManyToOne(fetch = FetchType.LAZY, optional=false) private Race race; @ManyToOne(fetch = FetchType.LAZY, optional=false) private Adherent proprietaire; // getters/setters }
Enum (ou une classe) :
public enum EtatChien { INSCRIT, RETIRE }
On veut :
ChienCreateDto
ChienDto
public record ChienCreateDto( @NotBlank String numeroTatouage, @NotBlank String nom, @NotNull Long raceId, @NotNull Long proprietaireId ) {}
DTO de sortie (pensé pour l’UI) :
public record ChienDto( Long id, String numeroTatouage, String nom, String etat, Long raceId, String raceCode, String raceNom, Long proprietaireId, String proprietaireNom ) {}
Avant, il faut installer les dépendances MapStruct dans votre fichier pom.xml. J’ai aussi mis Lombok.
pom.xml
Sans mapper :
setXxx(...)
Avec MapStruct :
Vous voyez qu’ici on donne des noms différents aux attributs qui seront retournés au front dans un format JSON.
@Mapper(componentModel = "spring") public interface ChienMapper { @Mapping(target="etat", expression="java(chien.getEtat().name())") // vous ne l'avez pas dans la première version, à enlever ! @Mapping(target="raceId", source="race.id") @Mapping(target="raceCode", source="race.code") @Mapping(target="raceNom", source="race.nom") @Mapping(target="proprietaireId", source="proprietaire.id") @Mapping(target="proprietaireNom", expression="java(chien.getProprietaire().getPrenom() + " " + chien.getProprietaire().getNom())") ChienDto toDto(Chien chien); }
Pas de changement !
public interface ChienRepository extends JpaRepository<Chien, Long> { boolean existsByNumeroTatouage(String numeroTatouage); }
Objectifs :
@Service public class ChienService { private final ChienRepository chienRepository; private final RaceRepository raceRepository; private final AdherentRepository adherentRepository; private final ChienMapper mapper; public ChienService(ChienRepository chienRepository, RaceRepository raceRepository, AdherentRepository adherentRepository, ChienMapper mapper) { this.chienRepository = chienRepo; this.raceRepository = raceRepo; this.adherentRepository = adherentRepository; // qui est le propriétaire dans Chien this.mapper = mapper; } @Transactional public ChienDto create(ChienCreateDto dto) { if (chienRepository.existsByNumeroTatouage(dto.numeroTatouage())) { throw new IllegalArgumentException("Tatouage déjà utilisé : " + dto.numeroTatouage()); } Race race = raceRepository.findById(dto.raceId()) .orElseThrow(() -> new IllegalArgumentException("Race inconnue id=" + dto.raceId())); Adherent p = adherentRepository.findById(dto.proprietaireId()) .orElseThrow(() -> new IllegalArgumentException("Propriétaire inconnu id=" + dto.proprietaireId())); Chien c = new Chien(); c.setNumeroTatouage(dto.numeroTatouage()); c.setNom(dto.nom()); c.setEtat(EtatChien.INSCRIT); c.setRace(race); c.setProprietaire(p); return mapper.toDto(chienRepository.save(c)); } }
@RestController @RequestMapping("/api/chiens") public class ChienController { private final ChienService chienService; public ChienController(ChienService chienService) { this.chienService = chienService; } @PostMapping public ResponseEntity<ChienDto> create(@Valid @RequestBody ChienCreateDto createDto) { return ResponseEntity.status(HttpStatus.CREATED).body(chienService.create(createDto)); } }
@RestController @RequestMapping("/api/chiens") public class ChienController { private final ChienService chienService; public ChienController(ChienService chienService) { this.chienService = chienService; } // Récupérer tous les chiens : GET /api/chiens @GetMapping public ResponseEntity<List<ChienDto>> getAllChiens() { List<ChienDto> chiens = chienService.getAllChiens(); return ResponseEntity.ok(chiens); } // Créer un chien : POST /api/chiens @PostMapping public ResponseEntity<ChienDto> createChien(@Valid @RequestBody ChienCreateDto createDto) { ChienDto savedChien = chienService.createChien(createDto); return ResponseEntity.ok(savedChien); } // Récupérer un chien par ID : GET /api/chiens/{id} @GetMapping("/{id}") public ResponseEntity<ChienDto> getChien(@PathVariable Long id) { ChienDto chienDto = chienService.getChienById(id); return ResponseEntity.ok(chienDto); } }
Remarques :
return ResponseEntity.status(HttpStatus.CREATED).body(chienService.create(dto))
On utilise HttpStatus.CREATED (201) : C’est le statut HTTP recommandé pour indiquer qu’une ressource a été créée avec succès. Le code 201 est semantiquement correct pour une opération POST (création).
Dans la version du contrôleur ci-dessous, on utilise une autre syntaxe : return ResponseEntity.ok(savedChien);
return ResponseEntity.ok(savedChien);
On utilise ResponseEntity.ok(), qui correspond à HttpStatus.OK (200). Le code 200 signifie que la requête a réussi, mais il est moins précis qu’un 201 pour une création.
savedChien
Version 1 : Le résultat de chienService.create(dto) est directement retourné sans être stocké dans une variable. Moins pratique si on veut ajouter des logs ou des traitements intermédiaires.
Version 2 : Le résultat est stocké dans savedChien ce qui permettrait d’ajouter des logs ou des traitements avant de retourner la réponse.
Exemple :
ChienDto savedChien = chienService.createChien(createDto); log.info("Chien créé avec succès : {}", savedChien); return ResponseEntity.ok(savedChien);
Le code idéal serait celui-ci :
@PostMapping public ResponseEntity<ChienDto> createChien(@Valid @RequestBody ChienCreateDto createDto) { ChienDto savedChien = chienService.createChien(createDto); log.info("Chien créé avec succès : {}", savedChien); return ResponseEntity.status(HttpStatus.CREATED).body(savedChien); // retourne 201 pour création }
Conclusion :
Exemple de structure
├── controller/ │ ├── AdherentController.java │ ├── ChienController.java │ ├── ConcoursController.java │ ├── RaceController.java │ └── EpreuveController.java │ └── ... ├── service/ │ ├── AdherentService.java │ ├── ChienService.java │ └── ... ├── dto/ │ ├── AdherentDto.java │ ├── AdherentCreateDto.java │ ├── AdherentUpdateDto.java │ ├── ChienDto.java │ ├── ChienCreateDto.java │ ├── ChienUpdateDto.java │ └── ... └── ...
curl -X POST http://localhost:8080/api/chiens \ -H "Content-Type: application/json" \ -d '{"numeroTatouage":"T123","nom":"Rex","raceId":1,"proprietaireId":1}'
Sinon, utilisez Swagger
FetchType.EAGER
GET /api/chiens/{id}
@PathVariable
Long id