diff --git a/pom.xml b/pom.xml
index 8576c22ba..cd60034b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
3.6.0
0.0.11
0.0.46
+ 2.5.0
@@ -135,6 +136,7 @@
junit-jupiter
test
+
org.testcontainers
mysql
@@ -145,7 +147,16 @@
jakarta.xml.bind
jakarta.xml.bind-api
-
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${spring-doc-version}
+
diff --git a/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java b/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java
index fa0630995..7b4570e38 100644
--- a/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java
+++ b/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java
@@ -18,6 +18,7 @@ package org.springframework.samples.petclinic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ImportRuntimeHints;
/**
@@ -26,6 +27,7 @@ import org.springframework.context.annotation.ImportRuntimeHints;
* @author Dave Syer
*/
@SpringBootApplication
+@EnableCaching
@ImportRuntimeHints(PetClinicRuntimeHints.class)
public class PetClinicApplication {
diff --git a/src/main/java/org/springframework/samples/petclinic/config/CacheConfig.java b/src/main/java/org/springframework/samples/petclinic/config/CacheConfig.java
new file mode 100644
index 000000000..5e0557c18
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/config/CacheConfig.java
@@ -0,0 +1,17 @@
+package org.springframework.samples.petclinic.config;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableCaching
+public class CacheConfig {
+
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager("petDetails");
+ }
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/controller/PetDetailController.java b/src/main/java/org/springframework/samples/petclinic/controller/PetDetailController.java
new file mode 100644
index 000000000..ffb57ed13
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/controller/PetDetailController.java
@@ -0,0 +1,73 @@
+package org.springframework.samples.petclinic.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.samples.petclinic.dto.PetDetailDto;
+import org.springframework.samples.petclinic.exception.ResourceNotFoundException;
+import org.springframework.samples.petclinic.model.PetDetail;
+import org.springframework.samples.petclinic.owner.Pet;
+import org.springframework.samples.petclinic.repository.PetDetailRepository;
+import org.springframework.samples.petclinic.service.PetDetailService;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/pet-details")
+@Tag(name = "PetDetails" , description = "Pet details apis")
+public class PetDetailController {
+
+ private PetDetailService service;
+ private PetDetailRepository petRepo;
+
+ @Autowired
+ public PetDetailController(PetDetailRepository petRepo, PetDetailService service) {
+ this.petRepo = petRepo;
+ this.service = service;
+ }
+
+ @PostMapping("/{petId}")
+ @Operation(summary = "create pet details")
+ public ResponseEntity createDetail(@PathVariable int petId,
+ @RequestBody PetDetailDto dto) {
+ Pet pet = petRepo.findByPetId(petId).get().getPet();
+ PetDetail detail = PetDetail.builder()
+ .pet(pet)
+ .temperament(dto.temperament())
+ .weight(dto.weight())
+ .length(dto.length())
+ .build();
+ return ResponseEntity.ok(service.savePetDetail(detail));
+ }
+
+ @GetMapping("/{petId}")
+ @Operation(summary = "Get pet details by per id")
+ public ResponseEntity getDetail(@PathVariable int petId) {
+ var detail = service.getPetDetailByPetId(petId);
+ return detail != null ? ResponseEntity.ok(detail) : ResponseEntity.notFound().build();
+ }
+
+ @PutMapping("/{petId}")
+ @Operation(summary = "Update pet detail")
+ public ResponseEntity updateDetail(@PathVariable int petId,
+ @RequestBody PetDetailDto dto) {
+ Pet pet = petRepo.findById(petId).orElseThrow(() ->
+ new ResourceNotFoundException("Pet not found with id: " + petId)).getPet();
+
+ PetDetail detail = PetDetail.builder()
+ .pet(pet)
+ .temperament(dto.temperament())
+ .weight(dto.weight())
+ .length(dto.length())
+ .build();
+
+ return ResponseEntity.ok(service.updatePetDetail(petId, detail));
+ }
+
+ @DeleteMapping("/{petId}")
+ @Operation(summary = "Delete pet detail by pet id")
+ public ResponseEntity deleteDetail(@PathVariable int petId) {
+ service.deletePetDetail(petId);
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/dto/PetDetailDto.java b/src/main/java/org/springframework/samples/petclinic/dto/PetDetailDto.java
new file mode 100644
index 000000000..b06c6b9ed
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/dto/PetDetailDto.java
@@ -0,0 +1,8 @@
+package org.springframework.samples.petclinic.dto;
+
+public record PetDetailDto(
+ String temperament,
+ Double weight,
+ Double length
+) {
+}
diff --git a/src/main/java/org/springframework/samples/petclinic/exception/GlobalExceptionHandler.java b/src/main/java/org/springframework/samples/petclinic/exception/GlobalExceptionHandler.java
new file mode 100644
index 000000000..db49a69d8
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/exception/GlobalExceptionHandler.java
@@ -0,0 +1,25 @@
+package org.springframework.samples.petclinic.exception;
+
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class GlobalExceptionHandler {
+ @ExceptionHandler(ResourceNotFoundException.class)
+ public ResponseEntity