From c4468a5e3d2c7406e420c4cae81cdcfd91de5862 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Mon, 2 Dec 2024 15:28:26 +0000 Subject: [PATCH] feat : implement code review comments --- .../config/JpaAuditConfig.java | 9 ++++++- .../model/request/AnimalRequest.java | 7 ++++++ .../model/response/AnimalResponse.java | 9 +++++++ .../repositories/CustomAnimalRepository.java | 12 ++++++++++ .../CustomAnimalRepositoryImpl.java | 4 ++-- .../services/AnimalService.java | 15 ++++++------ .../web/controllers/AnimalController.java | 24 +++++++++++++++---- .../web/controllers/AnimalControllerIT.java | 13 +++++++++- 8 files changed, 77 insertions(+), 16 deletions(-) diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/config/JpaAuditConfig.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/config/JpaAuditConfig.java index 5c20790bb..2d0565b39 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/config/JpaAuditConfig.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/config/JpaAuditConfig.java @@ -3,7 +3,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -// As JpaAuditing works based on Proxy we shouldn't create configuration as proxyBeans as false +/** + * Enables JPA auditing for automatic management of entity timestamps. + * Note: JPA Auditing works based on Proxy, so proxy beans must remain enabled. + * + * This configuration supports: + * - Automatic population of @CreatedDate in Auditable entities + * - Primarily used by Animal entity for tracking creation timestamps + */ @Configuration @EnableJpaAuditing public class JpaAuditConfig {} diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java index d2f8b9cb2..16a0f8691 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java @@ -2,6 +2,13 @@ import jakarta.validation.constraints.NotBlank; +/** + * Request object for animal operations containing essential animal information. + * + * @param name The name of the animal (required) + * @param type The type/species of the animal (required) + * @param habitat The natural environment where the animal lives (optional) + */ public record AnimalRequest( @NotBlank(message = "Name cannot be blank") String name, @NotBlank(message = "Type cannot be blank") String type, diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java index aabd58019..eec3f23d4 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java @@ -2,4 +2,13 @@ import java.time.LocalDateTime; +/** + * Response record representing an animal entity with its basic attributes and audit information. + * + * @param id Unique identifier of the animal + * @param name Name of the animal + * @param type Type/species of the animal + * @param habitat Natural environment where the animal lives + * @param created Timestamp when the animal record was created + */ public record AnimalResponse(Long id, String name, String type, String habitat, LocalDateTime created) {} diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepository.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepository.java index 2a1fb579a..515790aff 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepository.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepository.java @@ -6,6 +6,18 @@ import org.springframework.data.domain.Window; import org.springframework.data.jpa.domain.Specification; +/** + * Custom repository interface for efficient keyset pagination of Animal entities. + */ public interface CustomAnimalRepository { + + /** + * Finds all animals matching the given specification using keyset pagination. + * + * @param spec The specification to filter animals + * @param pageRequest The pagination information + * @param scrollPosition The current position in the result set + * @return A window containing the paginated results + */ Window findAll(Specification spec, PageRequest pageRequest, ScrollPosition scrollPosition); } diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepositoryImpl.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepositoryImpl.java index 66d6761fa..a0ec21fdf 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepositoryImpl.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/repositories/CustomAnimalRepositoryImpl.java @@ -6,14 +6,14 @@ import jakarta.persistence.criteria.Predicate; import java.util.List; import java.util.Map; -import jdk.jfr.Registered; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Window; import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Repository; -@Registered +@Repository public class CustomAnimalRepositoryImpl implements CustomAnimalRepository { private final EntityManager entityManager; diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/services/AnimalService.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/services/AnimalService.java index 5c786bfab..59be9d76f 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/services/AnimalService.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/services/AnimalService.java @@ -55,15 +55,14 @@ private Pageable createPageable(FindAnimalsQuery findAnimalsQuery) { } public Window searchAnimals(String name, String type, int pageSize, Long scrollId) { - Specification specification = null; + Specification specification = Specification.where(null); + if (name != null && !name.isEmpty()) { - specification = Specification.where(AnimalSpecifications.hasName(name)); - } else if (type != null && !type.isEmpty()) { - if (specification != null) { - specification = specification.and(AnimalSpecifications.hasType(type)); - } else { - specification = Specification.where(AnimalSpecifications.hasType(type)); - } + specification = specification.and(AnimalSpecifications.hasName(name)); + } + + if (type != null && !type.isEmpty()) { + specification = specification.and(AnimalSpecifications.hasType(type)); } // Create initial ScrollPosition or continue from the given scrollId diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java index 718866c76..9de8891ff 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java @@ -7,7 +7,13 @@ import com.example.keysetpagination.model.response.PagedResult; import com.example.keysetpagination.services.AnimalService; import com.example.keysetpagination.utils.AppConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import java.net.URI; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Window; @@ -46,12 +52,22 @@ PagedResult getAllAnimals( return animalService.findAllAnimals(findAnimalsQuery); } + @Operation(summary = "Search animals with keyset pagination support") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved animals"), + @ApiResponse(responseCode = "400", description = "Invalid parameters supplied") + }) @GetMapping("/search") public Window searchAnimals( - @RequestParam(required = false) String name, - @RequestParam(required = false) String type, - @RequestParam(defaultValue = "10") int pageSize, - @RequestParam(required = false) Long scrollId) { + @Parameter(description = "Animal name to search for") @RequestParam(required = false) String name, + @Parameter(description = "Animal type to filter by") @RequestParam(required = false) String type, + @Parameter(description = "Number of items per page (max 100)") + @RequestParam(defaultValue = "10") + @Min(1) + @Max(100) + int pageSize, + @Parameter(description = "Scroll ID for pagination") @RequestParam(required = false) Long scrollId) { return animalService.searchAnimals(name, type, pageSize, scrollId); } diff --git a/jpa/keyset-pagination/boot-data-window-pagination/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java b/jpa/keyset-pagination/boot-data-window-pagination/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java index 1f8b88493..44552d521 100644 --- a/jpa/keyset-pagination/boot-data-window-pagination/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java +++ b/jpa/keyset-pagination/boot-data-window-pagination/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java @@ -101,6 +101,15 @@ void shouldSearchAnimals() throws Exception { .andExpect(jsonPath("$.last", is(true))); } + @Test + void shouldReturnEmptyResultForNonExistentType() throws Exception { + this.mockMvc + .perform(get("/api/animals/search").param("type", "NonExistentType")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content", hasSize(0))) + .andExpect(jsonPath("$.last", is(true))); + } + @Test void shouldFindAnimalById() throws Exception { Animal animal = animalList.getFirst(); @@ -123,7 +132,9 @@ void shouldCreateNewAnimal() throws Exception { .andExpect(status().isCreated()) .andExpect(header().exists(HttpHeaders.LOCATION)) .andExpect(jsonPath("$.id", notNullValue())) - .andExpect(jsonPath("$.name", is(animalRequest.name()))); + .andExpect(jsonPath("$.name", is(animalRequest.name()))) + .andExpect(jsonPath("$.type", is(animalRequest.type()))) + .andExpect(jsonPath("$.habitat", is(animalRequest.habitat()))); } @Test