mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-16 04:45:49 +00:00
petDetails-enhancement changes
This commit is contained in:
parent
30aab0ae76
commit
9264c8b083
14 changed files with 353 additions and 1 deletions
13
pom.xml
13
pom.xml
|
@ -36,6 +36,7 @@
|
|||
<maven-checkstyle.version>3.6.0</maven-checkstyle.version>
|
||||
<nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version>
|
||||
<spring-format.version>0.0.46</spring-format.version>
|
||||
<spring-doc-version>2.5.0</spring-doc-version>
|
||||
|
||||
</properties>
|
||||
|
||||
|
@ -135,6 +136,7 @@
|
|||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
|
@ -145,7 +147,16 @@
|
|||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${spring-doc-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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<PetDetail> 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<PetDetail> 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<PetDetail> 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<Void> deleteDetail(@PathVariable int petId) {
|
||||
service.deletePetDetail(petId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.springframework.samples.petclinic.dto;
|
||||
|
||||
public record PetDetailDto(
|
||||
String temperament,
|
||||
Double weight,
|
||||
Double length
|
||||
) {
|
||||
}
|
|
@ -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<Map<String, String>> handleNotFound(ResourceNotFoundException ex) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", ex.getMessage());
|
||||
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, String>> handleGeneric(Exception ex) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "internal server error");
|
||||
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.springframework.samples.petclinic.exception;
|
||||
|
||||
|
||||
|
||||
public class ResourceNotFoundException extends RuntimeException {
|
||||
public ResourceNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.springframework.samples.petclinic.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.springframework.samples.petclinic.owner.Pet;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "pet_details")
|
||||
@Data
|
||||
@Builder
|
||||
public class PetDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
@OneToOne
|
||||
@JoinColumn(name = "pet_id", nullable = false)
|
||||
private Pet pet;
|
||||
private String temperament;
|
||||
private Double weight;
|
||||
private Double length;
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.springframework.samples.petclinic.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.expression.spel.ast.OpAnd;
|
||||
import org.springframework.samples.petclinic.model.PetDetail;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface PetDetailRepository extends JpaRepository<PetDetail, Integer> {
|
||||
Optional<PetDetail> findByPetId(int petId);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.springframework.samples.petclinic.service;
|
||||
|
||||
import org.springframework.samples.petclinic.dto.PetDetailDto;
|
||||
import org.springframework.samples.petclinic.model.PetDetail;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public interface PetDetailService {
|
||||
PetDetail savePetDetail(PetDetail petDetail);
|
||||
PetDetail getPetDetailByPetId(Integer petId);
|
||||
PetDetail updatePetDetail(Integer petId, PetDetail petDetail);
|
||||
void deletePetDetail(Integer petId);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.springframework.samples.petclinic.service;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
|
||||
import org.springframework.samples.petclinic.exception.ResourceNotFoundException;
|
||||
import org.springframework.samples.petclinic.model.PetDetail;
|
||||
import org.springframework.samples.petclinic.repository.PetDetailRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
@Service
|
||||
public class PetDetailServiceImpl implements PetDetailService {
|
||||
|
||||
private final PetDetailRepository petRepo;
|
||||
|
||||
@Autowired
|
||||
public PetDetailServiceImpl(PetDetailRepository petRepo) {
|
||||
this.petRepo = petRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public PetDetail savePetDetail(PetDetail petDetail) {
|
||||
return petRepo.save(petDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "petDetails", key = "#petId")
|
||||
@Transactional
|
||||
public PetDetail getPetDetailByPetId(Integer petId) {
|
||||
return petRepo.findByPetId(petId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet details not found for the pet id : " +petId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@CachePut(value = "petDetails", key = "#petId")
|
||||
@Transactional
|
||||
public PetDetail updatePetDetail(Integer petId, PetDetail updatedDetail) {
|
||||
PetDetail existing = petRepo.findByPetId(petId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet detail not found for the pet id: " + petId));
|
||||
|
||||
existing.setTemperament(updatedDetail.getTemperament());
|
||||
existing.setWeight(updatedDetail.getWeight());
|
||||
existing.setLength(updatedDetail.getLength());
|
||||
return petRepo.save(existing);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "petDetails", key = "#petId")
|
||||
@Transactional
|
||||
public void deletePetDetail(Integer petId) {
|
||||
PetDetail detail = petRepo.findByPetId(petId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet details not found for the for pet id: " + petId));
|
||||
petRepo.delete(detail);
|
||||
}
|
||||
}
|
30
src/main/resources/db/h2/create_petDetails_table.sql
Normal file
30
src/main/resources/db/h2/create_petDetails_table.sql
Normal file
|
@ -0,0 +1,30 @@
|
|||
CREATE TABLE pet_types (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(80) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE pets (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
birth_date DATE,
|
||||
type_id INT,
|
||||
FOREIGN KEY (type_id) REFERENCES pet_types(id)
|
||||
);
|
||||
|
||||
CREATE TABLE visits (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
pet_id INT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
description VARCHAR(255),
|
||||
FOREIGN KEY (pet_id) REFERENCES pets(id)
|
||||
);
|
||||
|
||||
CREATE TABLE pet_details (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
pet_id INT NOT NULL UNIQUE,
|
||||
temperament VARCHAR(100),
|
||||
weight DOUBLE,
|
||||
length DOUBLE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (pet_id) REFERENCES pets(id)
|
||||
);
|
|
@ -0,0 +1,24 @@
|
|||
package org.springframework.samples.petclinic.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@AutoConfigureMockMvc
|
||||
class PetDetailControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void testGetPetDetail() throws Exception {
|
||||
mockMvc.perform(get("/api/pet-details/1"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.springframework.samples.petclinic.service;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.samples.petclinic.model.PetDetail;
|
||||
import org.springframework.samples.petclinic.owner.Pet;
|
||||
import org.springframework.samples.petclinic.repository.PetDetailRepository;
|
||||
|
||||
@SpringBootTest
|
||||
class PetDetailServiceTest {
|
||||
|
||||
@Autowired
|
||||
private PetDetailService service;
|
||||
|
||||
@Autowired
|
||||
private PetDetailRepository petRepo;
|
||||
|
||||
@Test
|
||||
void testSaveAndFetchPetDetail() {
|
||||
Pet pet = petRepo.findAll().get(0).getPet();
|
||||
|
||||
PetDetail detail = PetDetail.builder()
|
||||
.pet(pet)
|
||||
.temperament("Aggressive")
|
||||
.weight(20.0)
|
||||
.length(30.0)
|
||||
.build();
|
||||
|
||||
PetDetail saved = service.savePetDetail(detail);
|
||||
Assertions.assertNotNull(saved.getId());
|
||||
|
||||
PetDetail fetched = service.getPetDetailByPetId(pet.getId());
|
||||
Assertions.assertEquals("Aggressive", fetched.getTemperament());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue