diff --git a/.github/labeler.yml b/.github/labeler.yml
index 6d8b8416d..2cdc79f43 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -66,10 +66,6 @@
- changed-files:
- any-glob-to-any-file:
- jpa/boot-data-envers/**/*
-"component: keyset-pagination-blaze":
- - changed-files:
- - any-glob-to-any-file:
- - jpa/boot-data-keyset-pagination/blaze-persistence/**/*
"component: multiple-datasources":
- changed-files:
- any-glob-to-any-file:
@@ -86,6 +82,14 @@
- changed-files:
- any-glob-to-any-file:
- jpa/boot-read-replica-postgresql/**/*
+"component: keyset-pagination-blaze":
+ - changed-files:
+ - any-glob-to-any-file:
+ - jpa/keyset-pagination/blaze-persistence/**/*
+"component: keyset-pagination-data-jpa":
+ - changed-files:
+ - any-glob-to-any-file:
+ - jpa/keyset-pagination/boot-data-window-paginiation/**/*
"component: jpa-multitenancy":
- changed-files:
- any-glob-to-any-file:
@@ -139,11 +143,12 @@
- jmh-benchmark/pom.xml
- jpa/boot-data-customsequence/pom.xml
- jpa/boot-data-envers/pom.xml
- - jpa/boot-data-keyset-pagination/blaze-persistence/pom.xml
- jpa/boot-data-multipledatasources/pom.xml
- jpa/boot-hibernate2ndlevelcache-sample/pom.xml
- jpa/boot-data-jpa-locks/pom.xml
- jpa/boot-read-replica-postgresql/pom.xml
+ - jpa/keyset-pagination/blaze-persistence/pom.xml
+ - jpa/keyset-pagination/boot-data-window-paginiation/pom.xml
- jpa/multitenancy/multitenancy-db/pom.xml
- jpa/multitenancy/multidatasource-multitenancy/pom.xml
- jpa/multitenancy/partition/pom.xml
diff --git a/.github/workflows/boot-jpa-keyset-pagination-blaze.yml b/.github/workflows/boot-jpa-keyset-pagination-blaze.yml
index db2a3be51..a787eea90 100644
--- a/.github/workflows/boot-jpa-keyset-pagination-blaze.yml
+++ b/.github/workflows/boot-jpa-keyset-pagination-blaze.yml
@@ -3,11 +3,11 @@ name: boot-data-keyset-pagination-blaze
on:
push:
paths:
- - "jpa/boot-data-keyset-pagination/blaze-persistence/**"
+ - "jpa/keyset-pagination/blaze-persistence/**"
branches: [main]
pull_request:
paths:
- - "jpa/boot-data-keyset-pagination/blaze-persistence/**"
+ - "jpa/keyset-pagination/blaze-persistence/**"
types:
- opened
- synchronize
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
- working-directory: "jpa/boot-data-keyset-pagination/blaze-persistence"
+ working-directory: "jpa/keyset-pagination/blaze-persistence"
steps:
- uses: actions/checkout@v4
with:
diff --git a/.github/workflows/boot-jpa-keyset-pagination-data-jpa.yml b/.github/workflows/boot-jpa-keyset-pagination-data-jpa.yml
new file mode 100644
index 000000000..614c50587
--- /dev/null
+++ b/.github/workflows/boot-jpa-keyset-pagination-data-jpa.yml
@@ -0,0 +1,39 @@
+name: boot-data-keyset-pagination
+
+on:
+ push:
+ paths:
+ - "jpa/keyset-pagination/boot-data-window-paginiation/**"
+ branches: [main]
+ pull_request:
+ paths:
+ - "jpa/keyset-pagination/boot-data-window-paginiation/**"
+ types:
+ - opened
+ - synchronize
+ - reopened
+
+jobs:
+ build:
+ name: Run Unit & Integration Tests
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: "jpa/keyset-pagination/boot-data-window-paginiation"
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4.5.0
+ with:
+ java-version: 21
+ distribution: "temurin"
+ cache: "maven"
+
+ - name: Grant execute permission for mvnw
+ run: chmod +x mvnw
+
+ - name: Build and analyze
+ run: ./mvnw clean verify
diff --git a/README.md b/README.md
index d6a5a989d..0919436cf 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ The following table list all sample codes related to the spring boot integration
| [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed |
| [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed |
| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application, demonstrated ability to create custom sequences, using datasource-proxy and LazyConnectionDataSourceProxy for db connection improvement using mariadb | Completed |
-| [KeySet pagination and dynamic search](./jpa/boot-data-keyset-pagination/blaze-persistence/) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed |
+| [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence/) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed |
For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/)
diff --git a/SUMMARY.md b/SUMMARY.md
index 11b6c6fe4..f969f07da 100644
--- a/SUMMARY.md
+++ b/SUMMARY.md
@@ -26,12 +26,13 @@
* [jpa](jpa/README.md)
* [Custom Sequence](jpa/boot-data-customsequence/README.md)
* [Hibernate Envers Implementation](jpa/boot-data-envers/README.md)
- * [KeySet Pagination Using Blaze](jpa/boot-data-keyset-pagination/blaze-persistence/README.md)
* [multiple datasources using Spring Boot](jpa/boot-data-multipledatasources/README.md),
* [spring-boot-hibernate2ndlevelcache-sample](jpa/boot-hibernate2ndlevelcache-sample/README.md)
* [JNDI in embedded Tomcat](jpa/boot-jndi-sample/README.md)
* [JPA locks implementation](jpa/boot-jpa-locks/README.md)
* [read-replica-with-spring-boot](jpa/boot-read-replica-postgresql/README.md)
+ * [KeySet Pagination Using Blaze](jpa/keyset-pagination/blaze-persistence/README.md)
+ * [KeySet Pagination Using Data-JPA](jpa/keyset-pagination/boot-data-window-pagination/README.md)
* [MultiTenancy using Hibernate in Spring Data JPA](jpa/multitenancy/README.md)
* [multitenancy-db](jpa/multitenancy/multitenancy-db/README.md)
* [multidatasource-multitenancy](jpa/multitenancy/multidatasource-multitenancy/README.md)
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/.github/workflows/maven.yml b/jpa/keyset-pagination/blaze-persistence/.github/workflows/maven.yml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/.github/workflows/maven.yml
rename to jpa/keyset-pagination/blaze-persistence/.github/workflows/maven.yml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/.gitignore b/jpa/keyset-pagination/blaze-persistence/.gitignore
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/.gitignore
rename to jpa/keyset-pagination/blaze-persistence/.gitignore
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.jar b/jpa/keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.jar
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.jar
rename to jpa/keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.jar
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.properties b/jpa/keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.properties
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.properties
rename to jpa/keyset-pagination/blaze-persistence/.mvn/wrapper/maven-wrapper.properties
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/.yo-rc.json b/jpa/keyset-pagination/blaze-persistence/.yo-rc.json
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/.yo-rc.json
rename to jpa/keyset-pagination/blaze-persistence/.yo-rc.json
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/Dockerfile b/jpa/keyset-pagination/blaze-persistence/Dockerfile
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/Dockerfile
rename to jpa/keyset-pagination/blaze-persistence/Dockerfile
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/Jenkinsfile b/jpa/keyset-pagination/blaze-persistence/Jenkinsfile
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/Jenkinsfile
rename to jpa/keyset-pagination/blaze-persistence/Jenkinsfile
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/README.md b/jpa/keyset-pagination/blaze-persistence/README.md
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/README.md
rename to jpa/keyset-pagination/blaze-persistence/README.md
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/docker/docker-compose-app.yml b/jpa/keyset-pagination/blaze-persistence/docker/docker-compose-app.yml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/docker/docker-compose-app.yml
rename to jpa/keyset-pagination/blaze-persistence/docker/docker-compose-app.yml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/docker/docker-compose.yml b/jpa/keyset-pagination/blaze-persistence/docker/docker-compose.yml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/docker/docker-compose.yml
rename to jpa/keyset-pagination/blaze-persistence/docker/docker-compose.yml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/lombok.config b/jpa/keyset-pagination/blaze-persistence/lombok.config
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/lombok.config
rename to jpa/keyset-pagination/blaze-persistence/lombok.config
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/mvnw b/jpa/keyset-pagination/blaze-persistence/mvnw
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/mvnw
rename to jpa/keyset-pagination/blaze-persistence/mvnw
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/mvnw.cmd b/jpa/keyset-pagination/blaze-persistence/mvnw.cmd
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/mvnw.cmd
rename to jpa/keyset-pagination/blaze-persistence/mvnw.cmd
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/pom.xml b/jpa/keyset-pagination/blaze-persistence/pom.xml
similarity index 99%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/pom.xml
rename to jpa/keyset-pagination/blaze-persistence/pom.xml
index 7176c9649..36982c37f 100644
--- a/jpa/boot-data-keyset-pagination/blaze-persistence/pom.xml
+++ b/jpa/keyset-pagination/blaze-persistence/pom.xml
@@ -11,9 +11,9 @@
com.example.keysetpagination
- boot-data-keyset-pagination
+ boot-data-keyset-pagination-blaze
0.0.1-SNAPSHOT
- boot-data-keyset-pagination
+ boot-data-keyset-pagination-blaze
boot-data-keyset-pagination
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/sonar-project.properties b/jpa/keyset-pagination/blaze-persistence/sonar-project.properties
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/sonar-project.properties
rename to jpa/keyset-pagination/blaze-persistence/sonar-project.properties
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/Application.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/Application.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/Application.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/Application.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/Initializer.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/Initializer.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/Initializer.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/Initializer.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SpringBlazePersistenceConfiguration.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SpringBlazePersistenceConfiguration.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SpringBlazePersistenceConfiguration.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SpringBlazePersistenceConfiguration.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/Loggable.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/Loggable.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/Loggable.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/Loggable.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/entities/Actor.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/entities/Actor.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/entities/Actor.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/entities/Actor.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ActorNotFoundException.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ActorNotFoundException.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ActorNotFoundException.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ActorNotFoundException.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/mapper/ActorMapper.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/mapper/ActorMapper.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/mapper/ActorMapper.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/mapper/ActorMapper.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/FindActorsQuery.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/FindActorsQuery.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/FindActorsQuery.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/FindActorsQuery.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/SearchCriteria.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/SearchCriteria.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/SearchCriteria.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/query/SearchCriteria.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/request/ActorRequest.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/request/ActorRequest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/request/ActorRequest.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/request/ActorRequest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/ActorResponse.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/ActorResponse.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/ActorResponse.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/ActorResponse.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/KeySetPageResponse.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/KeySetPageResponse.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/KeySetPageResponse.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/KeySetPageResponse.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/PagedResult.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/PagedResult.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/PagedResult.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/model/response/PagedResult.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/repositories/ActorRepository.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/repositories/ActorRepository.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/repositories/ActorRepository.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/repositories/ActorRepository.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/services/ActorService.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/services/ActorService.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/services/ActorService.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/services/ActorService.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/AppConstants.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/AppConstants.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/AppConstants.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/AppConstants.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/EntitySpecification.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/EntitySpecification.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/EntitySpecification.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/EntitySpecification.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/FilterAttributesProvider.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/FilterAttributesProvider.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/FilterAttributesProvider.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/FilterAttributesProvider.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/QueryOperatorHandler.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/QueryOperatorHandler.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/QueryOperatorHandler.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/utils/QueryOperatorHandler.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/web/controllers/ActorController.java b/jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/web/controllers/ActorController.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/web/controllers/ActorController.java
rename to jpa/keyset-pagination/blaze-persistence/src/main/java/com/example/keysetpagination/web/controllers/ActorController.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/application-local.properties b/jpa/keyset-pagination/blaze-persistence/src/main/resources/application-local.properties
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/application-local.properties
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/application-local.properties
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/application.properties b/jpa/keyset-pagination/blaze-persistence/src/main/resources/application.properties
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/application.properties
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/application.properties
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/db.changelog-master.yaml b/jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/db.changelog-master.yaml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/db.changelog-master.yaml
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/db.changelog-master.yaml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/01-init.xml b/jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/01-init.xml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/01-init.xml
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/01-init.xml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/02-create_actors_table.xml b/jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/02-create_actors_table.xml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/02-create_actors_table.xml
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/db/changelog/migration/02-create_actors_table.xml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/logback-spring.xml b/jpa/keyset-pagination/blaze-persistence/src/main/resources/logback-spring.xml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/main/resources/logback-spring.xml
rename to jpa/keyset-pagination/blaze-persistence/src/main/resources/logback-spring.xml
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/SchemaValidationTest.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/SchemaValidationTest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/SchemaValidationTest.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/SchemaValidationTest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/TestApplication.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/TestApplication.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/TestApplication.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/TestApplication.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/ContainersConfig.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/ContainersConfig.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/ContainersConfig.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/common/ContainersConfig.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/services/ActorServiceTest.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/services/ActorServiceTest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/services/ActorServiceTest.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/services/ActorServiceTest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerIT.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerIT.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerIT.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerIT.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerTest.java b/jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerTest.java
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerTest.java
rename to jpa/keyset-pagination/blaze-persistence/src/test/java/com/example/keysetpagination/web/controllers/ActorControllerTest.java
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/resources/application-test.properties b/jpa/keyset-pagination/blaze-persistence/src/test/resources/application-test.properties
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/resources/application-test.properties
rename to jpa/keyset-pagination/blaze-persistence/src/test/resources/application-test.properties
diff --git a/jpa/boot-data-keyset-pagination/blaze-persistence/src/test/resources/logback-test.xml b/jpa/keyset-pagination/blaze-persistence/src/test/resources/logback-test.xml
similarity index 100%
rename from jpa/boot-data-keyset-pagination/blaze-persistence/src/test/resources/logback-test.xml
rename to jpa/keyset-pagination/blaze-persistence/src/test/resources/logback-test.xml
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/.github/workflows/maven.yml b/jpa/keyset-pagination/boot-data-window-paginiation/.github/workflows/maven.yml
new file mode 100644
index 000000000..e694d6736
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/.github/workflows/maven.yml
@@ -0,0 +1,43 @@
+name: CI Build
+
+on:
+ push:
+ branches:
+ - "**"
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ distribution: [ 'temurin' ]
+ java: [ '17' ]
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Java ${{ matrix.java }}
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: ${{ matrix.distribution }}
+ cache: 'maven'
+
+ - name: Grant execute permission for mvnw
+ run: chmod +x mvnw
+
+ - name: Build with Maven
+ run: ./mvnw clean verify
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: SonarQube Scan
+ run: ./mvnw compile sonar:sonar -Dsonar.login=${{ secrets.SONAR_TOKEN }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - if: ${{ github.ref == 'refs/heads/main' }}
+ name: Build and Publish Docker Image
+ run: |
+ docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
+ ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=${{ secrets.DOCKER_USERNAME }}/boot-data-window-paginiation
+ docker push ${{ secrets.DOCKER_USERNAME }}/boot-data-window-paginiation
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/.gitignore b/jpa/keyset-pagination/boot-data-window-paginiation/.gitignore
new file mode 100644
index 000000000..c456913cf
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/.gitignore
@@ -0,0 +1,37 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Misc ###
+*.log
+.DS_Store
\ No newline at end of file
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.jar b/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..bf82ff01c
Binary files /dev/null and b/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.properties b/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..f95f1ee80
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/.yo-rc.json b/jpa/keyset-pagination/boot-data-window-paginiation/.yo-rc.json
new file mode 100644
index 000000000..29864ed96
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/.yo-rc.json
@@ -0,0 +1,13 @@
+{
+ "generator-springboot": {
+ "appName": "boot-data-window-paginiation",
+ "packageName": "com.example.keysetpagination",
+ "databaseType": "postgresql",
+ "dbMigrationTool": "liquibase",
+ "dbMigrationFormat": "xml",
+ "features": [],
+ "buildTool": "maven",
+ "packageFolder": "com/example/keysetpagination",
+ "liquibaseMigrationCounter": 2
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/Dockerfile b/jpa/keyset-pagination/boot-data-window-paginiation/Dockerfile
new file mode 100644
index 000000000..9673dcd39
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/Dockerfile
@@ -0,0 +1,14 @@
+FROM eclipse-temurin:17.0.11_9-jre-focal as builder
+WORKDIR application
+ARG JAR_FILE=target/boot-data-window-paginiation-0.0.1-SNAPSHOT.jar
+COPY ${JAR_FILE} application.jar
+RUN java -Djarmode=layertools -jar application.jar extract
+
+# the second stage of our build will copy the extracted layers
+FROM eclipse-temurin:17.0.11_9-jre-focal
+WORKDIR application
+COPY --from=builder application/dependencies/ ./
+COPY --from=builder application/spring-boot-loader/ ./
+COPY --from=builder application/snapshot-dependencies/ ./
+COPY --from=builder application/application/ ./
+ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/Jenkinsfile b/jpa/keyset-pagination/boot-data-window-paginiation/Jenkinsfile
new file mode 100644
index 000000000..de65a8185
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/Jenkinsfile
@@ -0,0 +1,19 @@
+pipeline {
+ agent any
+
+ triggers {
+ pollSCM('* * * * *')
+ }
+
+ environment {
+ APPLICATION_NAME = 'boot-data-window-paginiation'
+ }
+
+ stages {
+ stage('Build') {
+ steps {
+ sh './mvnw clean verify'
+ }
+ }
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/README.md b/jpa/keyset-pagination/boot-data-window-paginiation/README.md
new file mode 100644
index 000000000..5204af436
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/README.md
@@ -0,0 +1,33 @@
+# boot-data-window-paginiation
+
+### Format code
+
+```shell
+$ ./mvnw spotless:apply
+```
+
+### Run tests
+
+```shell
+$ ./mvnw clean verify
+```
+
+### Run locally
+
+```shell
+$ docker-compose -f docker/docker-compose.yml up -d
+$ ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
+```
+
+### Using Testcontainers at Development Time
+You can run `TestApplication.java` from your IDE directly.
+You can also run the application using Maven as follows:
+
+```shell
+./mvnw spring-boot:test-run
+```
+
+
+### Useful Links
+* Swagger UI: http://localhost:8080/swagger-ui.html
+* Actuator Endpoint: http://localhost:8080/actuator
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose-app.yml b/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose-app.yml
new file mode 100644
index 000000000..7fc2cef55
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose-app.yml
@@ -0,0 +1,17 @@
+version: '3.8'
+services:
+
+ boot-data-window-paginiation:
+ build: ..
+ ports:
+ - "18080:8080"
+ - "18787:8787"
+ restart: always
+ depends_on:
+ - postgresqldb
+ environment:
+ - SPRING_PROFILES_ACTIVE=docker
+ - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresqldb:5432/appdb
+ - SPRING_DATASOURCE_USERNAME=appuser
+ - SPRING_DATASOURCE_PASSWORD=secret
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose.yml b/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose.yml
new file mode 100644
index 000000000..57b244a6c
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/docker/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '3.8'
+services:
+
+ postgresqldb:
+ image: postgres:16.3-alpine
+ environment:
+ - POSTGRES_USER=appuser
+ - POSTGRES_PASSWORD=secret
+ - POSTGRES_DB=appdb
+ ports:
+ - "5432:5432"
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/lombok.config b/jpa/keyset-pagination/boot-data-window-paginiation/lombok.config
new file mode 100644
index 000000000..7a21e8804
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/mvnw b/jpa/keyset-pagination/boot-data-window-paginiation/mvnw
new file mode 100755
index 000000000..19529ddf8
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/mvnw
@@ -0,0 +1,259 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.2
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/mvnw.cmd b/jpa/keyset-pagination/boot-data-window-paginiation/mvnw.cmd
new file mode 100644
index 000000000..b150b91ed
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/mvnw.cmd
@@ -0,0 +1,149 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/pom.xml b/jpa/keyset-pagination/boot-data-window-paginiation/pom.xml
new file mode 100644
index 000000000..9f05709f7
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/pom.xml
@@ -0,0 +1,329 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.5
+
+
+ com.example.keysetpagination
+ boot-data-window-paginiation
+ 0.0.1-SNAPSHOT
+ boot-data-window-paginiation
+ boot-data-window-paginiation
+
+
+ UTF-8
+ UTF-8
+
+ 21
+ 2.6.0
+
+ ${project.build.directory}/test-results
+ 2.43.0
+ 10.0.3
+ 1.2.1
+ 4.0.0.4121
+ 0.8.12
+ 0.80
+ ${project.build.directory}/jacoco/test
+ ${jacoco.utReportFolder}/test.exec
+ ${project.build.directory}/jacoco/integrationTest
+ ${jacoco.itReportFolder}/integrationTest.exec
+ ${project.testresult.directory}/test
+ ${project.testresult.directory}/integrationTest
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.liquibase
+ liquibase-core
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc-openapi.version}
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ build-info
+
+
+
+
+
+ io.github.git-commit-id
+ git-commit-id-maven-plugin
+
+
+
+ revision
+
+
+
+
+ false
+ false
+ true
+
+ ^git.branch$
+ ^git.commit.id.abbrev$
+ ^git.commit.user.name$
+ ^git.commit.message.full$
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ alphabetical
+ ${junit.utReportFolder}
+
+ **/*IT*
+ **/*IntTest*
+ **/*IntegrationTest*
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+ ${project.build.outputDirectory}
+ alphabetical
+ ${junit.itReportFolder}
+
+ **/*IT*
+ **/*IntTest*
+ **/*IntegrationTest*
+
+
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+ org.codehaus.mojo
+ properties-maven-plugin
+ ${properties-maven-plugin.version}
+
+
+ initialize
+
+ read-project-properties
+
+
+
+ sonar-project.properties
+
+
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ ${spotless.version}
+
+
+
+ 2.50.0
+
+
+
+
+
+
+
+
+ compile
+
+ check
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+ ${project.build.directory}/jacoco/test/jacoco.exec
+
+
+ BUNDLE
+
+
+ COMPLEXITY
+ COVEREDRATIO
+ ${jacoco.minimum.coverage}
+
+
+
+
+
+ **/*Application.*
+ **/config/**
+ **/models/*
+ **/dtos/*
+ **/exceptions/*
+ **/*Constants*
+
+
+
+
+ pre-unit-tests
+
+ prepare-agent
+
+
+
+ ${jacoco.utReportFile}
+
+
+
+
+ post-unit-test
+ test
+
+ report
+
+
+ ${jacoco.utReportFile}
+ ${jacoco.utReportFolder}
+
+
+
+ pre-integration-tests
+
+ prepare-agent-integration
+
+
+
+ ${jacoco.itReportFile}
+
+
+
+
+ post-integration-tests
+ post-integration-test
+
+ report-integration
+
+
+ ${jacoco.itReportFile}
+ ${jacoco.itReportFolder}
+
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ ${dependency-check-maven.version}
+
+ false
+
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ ${sonar-maven-plugin.version}
+
+
+
+ sonar
+
+
+
+
+
+
+
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/sonar-project.properties b/jpa/keyset-pagination/boot-data-window-paginiation/sonar-project.properties
new file mode 100644
index 000000000..922e58e66
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/sonar-project.properties
@@ -0,0 +1,12 @@
+sonar.sourceEncoding=UTF-8
+sonar.projectKey=sonar_projectkey
+sonar.organization=sonar_org
+sonar.host.url=https://sonarcloud.io
+
+sonar.sources=src/main/java
+sonar.tests=src/test/java
+sonar.exclusions=src/main/java/**/config/*.*,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.*
+sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java
+sonar.java.codeCoveragePlugin=jacoco
+sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml
+sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/Application.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/Application.java
new file mode 100644
index 000000000..23c8ecfad
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/Application.java
@@ -0,0 +1,15 @@
+package com.example.keysetpagination;
+
+import com.example.keysetpagination.config.ApplicationProperties;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication
+@EnableConfigurationProperties({ApplicationProperties.class})
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java
new file mode 100644
index 000000000..e8985e285
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/ApplicationProperties.java
@@ -0,0 +1,22 @@
+package com.example.keysetpagination.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+@Data
+@ConfigurationProperties("application")
+public class ApplicationProperties {
+
+ @NestedConfigurationProperty
+ private Cors cors = new Cors();
+
+ @Data
+ public static class Cors {
+ private String pathPattern = "/api/**";
+ private String allowedMethods = "*";
+ private String allowedHeaders = "*";
+ private String allowedOriginPatterns = "*";
+ private boolean allowCredentials = true;
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java
new file mode 100644
index 000000000..effe64bd2
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/GlobalExceptionHandler.java
@@ -0,0 +1,61 @@
+package com.example.keysetpagination.config;
+
+import com.example.keysetpagination.exception.ResourceNotFoundException;
+import java.net.URI;
+import java.time.Instant;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ProblemDetail;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+@Order(Ordered.HIGHEST_PRECEDENCE)
+class GlobalExceptionHandler {
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ ProblemDetail onException(MethodArgumentNotValidException methodArgumentNotValidException) {
+ ProblemDetail problemDetail =
+ ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(400), "Invalid request content.");
+ problemDetail.setTitle("Constraint Violation");
+ List validationErrorsList = methodArgumentNotValidException.getAllErrors().stream()
+ .map(objectError -> {
+ FieldError fieldError = (FieldError) objectError;
+ return new ApiValidationError(
+ fieldError.getObjectName(),
+ fieldError.getField(),
+ fieldError.getRejectedValue(),
+ Objects.requireNonNull(fieldError.getDefaultMessage(), ""));
+ })
+ .sorted(Comparator.comparing(ApiValidationError::field))
+ .toList();
+ problemDetail.setProperty("violations", validationErrorsList);
+ return problemDetail;
+ }
+
+ @ExceptionHandler(Exception.class)
+ ProblemDetail onException(Exception exception) {
+ if (exception instanceof ResourceNotFoundException resourceNotFoundException) {
+ ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
+ resourceNotFoundException.getHttpStatus(), resourceNotFoundException.getMessage());
+ problemDetail.setTitle("Not Found");
+ problemDetail.setType(URI.create("http://api.boot-data-window-paginiation.com/errors/not-found"));
+ problemDetail.setProperty("errorCategory", "Generic");
+ problemDetail.setProperty("timestamp", Instant.now());
+ return problemDetail;
+ } else {
+ return ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
+ }
+ }
+
+ record ApiValidationError(String object, String field, Object rejectedValue, String message) {}
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/Initializer.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/Initializer.java
new file mode 100644
index 000000000..38fbb00a4
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/Initializer.java
@@ -0,0 +1,19 @@
+package com.example.keysetpagination.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+class Initializer implements CommandLineRunner {
+
+ private final ApplicationProperties properties;
+
+ @Override
+ public void run(String... args) {
+ log.info("Running Initializer.....");
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java
new file mode 100644
index 000000000..b658afa38
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/SwaggerConfig.java
@@ -0,0 +1,10 @@
+package com.example.keysetpagination.config;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.servers.Server;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@OpenAPIDefinition(info = @Info(title = "boot-data-window-paginiation", version = "v1"), servers = @Server(url = "/"))
+class SwaggerConfig {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java
new file mode 100644
index 000000000..f161bc105
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/WebMvcConfig.java
@@ -0,0 +1,21 @@
+package com.example.keysetpagination.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@RequiredArgsConstructor
+class WebMvcConfig implements WebMvcConfigurer {
+ private final ApplicationProperties properties;
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping(properties.getCors().getPathPattern())
+ .allowedMethods(properties.getCors().getAllowedMethods())
+ .allowedHeaders(properties.getCors().getAllowedHeaders())
+ .allowedOriginPatterns(properties.getCors().getAllowedOriginPatterns())
+ .allowCredentials(properties.getCors().isAllowCredentials());
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/Loggable.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/Loggable.java
new file mode 100644
index 000000000..3b9dd65ff
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/Loggable.java
@@ -0,0 +1,14 @@
+package com.example.keysetpagination.config.logging;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+@Inherited
+public @interface Loggable {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java
new file mode 100644
index 000000000..9937a326d
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/config/logging/LoggingAspect.java
@@ -0,0 +1,81 @@
+package com.example.keysetpagination.config.logging;
+
+import com.example.keysetpagination.utils.AppConstants;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.Profiles;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+class LoggingAspect {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final Environment env;
+
+ public LoggingAspect(Environment env) {
+ this.env = env;
+ }
+
+ @Pointcut("within(@org.springframework.stereotype.Repository *)"
+ + " || within(@org.springframework.stereotype.Service *)"
+ + " || within(@org.springframework.web.bind.annotation.RestController *)")
+ public void springBeanPointcut() {
+ // pointcut definition
+ }
+
+ @Pointcut("@within(com.example.keysetpagination.config.logging.Loggable) || "
+ + "@annotation(com.example.keysetpagination.config.logging.Loggable)")
+ public void applicationPackagePointcut() {
+ // pointcut definition
+ }
+
+ @AfterThrowing(pointcut = "applicationPackagePointcut()", throwing = "e")
+ public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
+ if (env.acceptsProfiles(Profiles.of(AppConstants.PROFILE_NOT_PROD))) {
+ log.error(
+ "Exception in {}.{}() with cause = '{}' and exception = '{}'",
+ joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(),
+ e.getCause() == null ? "NULL" : e.getCause(),
+ e.getMessage(),
+ e);
+
+ } else {
+ log.error(
+ "Exception in {}.{}() with cause = {}",
+ joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(),
+ e.getCause() == null ? "NULL" : e.getCause());
+ }
+ }
+
+ @Around("applicationPackagePointcut()")
+ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ if (log.isTraceEnabled()) {
+ log.trace(
+ "Enter: {}.{}()",
+ joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName());
+ }
+ long start = System.currentTimeMillis();
+ Object result = joinPoint.proceed();
+ long end = System.currentTimeMillis();
+ if (log.isTraceEnabled()) {
+ log.trace(
+ "Exit: {}.{}(). Time taken: {} millis",
+ joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(),
+ end - start);
+ }
+ return result;
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/entities/Animal.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/entities/Animal.java
new file mode 100644
index 000000000..903fe7c5c
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/entities/Animal.java
@@ -0,0 +1,43 @@
+package com.example.keysetpagination.entities;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import java.util.Objects;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.Hibernate;
+
+@Entity
+@Table(name = "animals")
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class Animal {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ private Long id;
+
+ @Column(nullable = false)
+ private String text;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
+ Animal animal = (Animal) o;
+ return id != null && Objects.equals(id, animal.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return getClass().hashCode();
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/AnimalNotFoundException.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/AnimalNotFoundException.java
new file mode 100644
index 000000000..a87f9b89d
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/AnimalNotFoundException.java
@@ -0,0 +1,8 @@
+package com.example.keysetpagination.exception;
+
+public class AnimalNotFoundException extends ResourceNotFoundException {
+
+ public AnimalNotFoundException(Long id) {
+ super("Animal with Id '%d' not found".formatted(id));
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java
new file mode 100644
index 000000000..34fc7d182
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/exception/ResourceNotFoundException.java
@@ -0,0 +1,17 @@
+package com.example.keysetpagination.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
+
+public class ResourceNotFoundException extends RuntimeException {
+
+ private static final HttpStatus httpStatus = HttpStatus.NOT_FOUND;
+
+ public ResourceNotFoundException(String errorMessage) {
+ super(errorMessage);
+ }
+
+ public HttpStatusCode getHttpStatus() {
+ return httpStatus;
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/mapper/AnimalMapper.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/mapper/AnimalMapper.java
new file mode 100644
index 000000000..8a30fe3c2
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/mapper/AnimalMapper.java
@@ -0,0 +1,29 @@
+package com.example.keysetpagination.mapper;
+
+import com.example.keysetpagination.entities.Animal;
+import com.example.keysetpagination.model.request.AnimalRequest;
+import com.example.keysetpagination.model.response.AnimalResponse;
+import java.util.List;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AnimalMapper {
+
+ public Animal toEntity(AnimalRequest animalRequest) {
+ Animal animal = new Animal();
+ animal.setText(animalRequest.text());
+ return animal;
+ }
+
+ public void mapAnimalWithRequest(Animal animal, AnimalRequest animalRequest) {
+ animal.setText(animalRequest.text());
+ }
+
+ public AnimalResponse toResponse(Animal animal) {
+ return new AnimalResponse(animal.getId(), animal.getText());
+ }
+
+ public List toResponseList(List animalList) {
+ return animalList.stream().map(this::toResponse).toList();
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/query/FindAnimalsQuery.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/query/FindAnimalsQuery.java
new file mode 100644
index 000000000..459bef4e3
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/query/FindAnimalsQuery.java
@@ -0,0 +1,3 @@
+package com.example.keysetpagination.model.query;
+
+public record FindAnimalsQuery(int pageNo, int pageSize, String sortBy, String sortDir) {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java
new file mode 100644
index 000000000..47f386722
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/request/AnimalRequest.java
@@ -0,0 +1,5 @@
+package com.example.keysetpagination.model.request;
+
+import jakarta.validation.constraints.NotEmpty;
+
+public record AnimalRequest(@NotEmpty(message = "Text cannot be empty") String text) {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java
new file mode 100644
index 000000000..2716c4e0c
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/AnimalResponse.java
@@ -0,0 +1,3 @@
+package com.example.keysetpagination.model.response;
+
+public record AnimalResponse(Long id, String text) {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/PagedResult.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/PagedResult.java
new file mode 100644
index 000000000..8f912c78a
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/model/response/PagedResult.java
@@ -0,0 +1,27 @@
+package com.example.keysetpagination.model.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import org.springframework.data.domain.Page;
+
+public record PagedResult(
+ List data,
+ long totalElements,
+ int pageNumber,
+ int totalPages,
+ @JsonProperty("isFirst") boolean isFirst,
+ @JsonProperty("isLast") boolean isLast,
+ @JsonProperty("hasNext") boolean hasNext,
+ @JsonProperty("hasPrevious") boolean hasPrevious) {
+ public PagedResult(Page page, List data) {
+ this(
+ data,
+ page.getTotalElements(),
+ page.getNumber() + 1,
+ page.getTotalPages(),
+ page.isFirst(),
+ page.isLast(),
+ page.hasNext(),
+ page.hasPrevious());
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/repositories/AnimalRepository.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/repositories/AnimalRepository.java
new file mode 100644
index 000000000..28ad8515e
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/repositories/AnimalRepository.java
@@ -0,0 +1,6 @@
+package com.example.keysetpagination.repositories;
+
+import com.example.keysetpagination.entities.Animal;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AnimalRepository extends JpaRepository {}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/services/AnimalService.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/services/AnimalService.java
new file mode 100644
index 000000000..ba38ac8da
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/services/AnimalService.java
@@ -0,0 +1,78 @@
+package com.example.keysetpagination.services;
+
+import com.example.keysetpagination.entities.Animal;
+import com.example.keysetpagination.exception.AnimalNotFoundException;
+import com.example.keysetpagination.mapper.AnimalMapper;
+import com.example.keysetpagination.model.query.FindAnimalsQuery;
+import com.example.keysetpagination.model.request.AnimalRequest;
+import com.example.keysetpagination.model.response.AnimalResponse;
+import com.example.keysetpagination.model.response.PagedResult;
+import com.example.keysetpagination.repositories.AnimalRepository;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional(readOnly = true)
+@RequiredArgsConstructor
+public class AnimalService {
+
+ private final AnimalRepository animalRepository;
+ private final AnimalMapper animalMapper;
+
+ public PagedResult findAllAnimals(FindAnimalsQuery findAnimalsQuery) {
+
+ // create Pageable instance
+ Pageable pageable = createPageable(findAnimalsQuery);
+
+ Page animalsPage = animalRepository.findAll(pageable);
+
+ List animalResponseList = animalMapper.toResponseList(animalsPage.getContent());
+
+ return new PagedResult<>(animalsPage, animalResponseList);
+ }
+
+ private Pageable createPageable(FindAnimalsQuery findAnimalsQuery) {
+ int pageNo = Math.max(findAnimalsQuery.pageNo() - 1, 0);
+ Sort sort = Sort.by(
+ findAnimalsQuery.sortDir().equalsIgnoreCase(Sort.Direction.ASC.name())
+ ? Sort.Order.asc(findAnimalsQuery.sortBy())
+ : Sort.Order.desc(findAnimalsQuery.sortBy()));
+ return PageRequest.of(pageNo, findAnimalsQuery.pageSize(), sort);
+ }
+
+ public Optional findAnimalById(Long id) {
+ return animalRepository.findById(id).map(animalMapper::toResponse);
+ }
+
+ @Transactional
+ public AnimalResponse saveAnimal(AnimalRequest animalRequest) {
+ Animal animal = animalMapper.toEntity(animalRequest);
+ Animal savedAnimal = animalRepository.save(animal);
+ return animalMapper.toResponse(savedAnimal);
+ }
+
+ @Transactional
+ public AnimalResponse updateAnimal(Long id, AnimalRequest animalRequest) {
+ Animal animal = animalRepository.findById(id).orElseThrow(() -> new AnimalNotFoundException(id));
+
+ // Update the animal object with data from animalRequest
+ animalMapper.mapAnimalWithRequest(animal, animalRequest);
+
+ // Save the updated animal object
+ Animal updatedAnimal = animalRepository.save(animal);
+
+ return animalMapper.toResponse(updatedAnimal);
+ }
+
+ @Transactional
+ public void deleteAnimalById(Long id) {
+ animalRepository.deleteById(id);
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/utils/AppConstants.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/utils/AppConstants.java
new file mode 100644
index 000000000..485ff931f
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/utils/AppConstants.java
@@ -0,0 +1,12 @@
+package com.example.keysetpagination.utils;
+
+public final class AppConstants {
+ public static final String PROFILE_PROD = "prod";
+ public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD;
+ public static final String PROFILE_TEST = "test";
+ public static final String PROFILE_NOT_TEST = "!" + PROFILE_TEST;
+ public static final String DEFAULT_PAGE_NUMBER = "0";
+ public static final String DEFAULT_PAGE_SIZE = "10";
+ public static final String DEFAULT_SORT_BY = "id";
+ public static final String DEFAULT_SORT_DIRECTION = "asc";
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java
new file mode 100644
index 000000000..53424239e
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/java/com/example/keysetpagination/web/controllers/AnimalController.java
@@ -0,0 +1,83 @@
+package com.example.keysetpagination.web.controllers;
+
+import com.example.keysetpagination.exception.AnimalNotFoundException;
+import com.example.keysetpagination.model.query.FindAnimalsQuery;
+import com.example.keysetpagination.model.request.AnimalRequest;
+import com.example.keysetpagination.model.response.AnimalResponse;
+import com.example.keysetpagination.model.response.PagedResult;
+import com.example.keysetpagination.services.AnimalService;
+import com.example.keysetpagination.utils.AppConstants;
+import jakarta.validation.Valid;
+import java.net.URI;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+@RestController
+@RequestMapping("/api/animals")
+@Slf4j
+@RequiredArgsConstructor
+class AnimalController {
+
+ private final AnimalService animalService;
+
+ @GetMapping
+ PagedResult getAllAnimals(
+ @RequestParam(value = "pageNo", defaultValue = AppConstants.DEFAULT_PAGE_NUMBER, required = false)
+ int pageNo,
+ @RequestParam(value = "pageSize", defaultValue = AppConstants.DEFAULT_PAGE_SIZE, required = false)
+ int pageSize,
+ @RequestParam(value = "sortBy", defaultValue = AppConstants.DEFAULT_SORT_BY, required = false)
+ String sortBy,
+ @RequestParam(value = "sortDir", defaultValue = AppConstants.DEFAULT_SORT_DIRECTION, required = false)
+ String sortDir) {
+ FindAnimalsQuery findAnimalsQuery = new FindAnimalsQuery(pageNo, pageSize, sortBy, sortDir);
+ return animalService.findAllAnimals(findAnimalsQuery);
+ }
+
+ @GetMapping("/{id}")
+ ResponseEntity getAnimalById(@PathVariable Long id) {
+ return animalService
+ .findAnimalById(id)
+ .map(ResponseEntity::ok)
+ .orElseThrow(() -> new AnimalNotFoundException(id));
+ }
+
+ @PostMapping
+ ResponseEntity createAnimal(@RequestBody @Validated AnimalRequest animalRequest) {
+ AnimalResponse response = animalService.saveAnimal(animalRequest);
+ URI location = ServletUriComponentsBuilder.fromCurrentRequest()
+ .path("/api/animals/{id}")
+ .buildAndExpand(response.id())
+ .toUri();
+ return ResponseEntity.created(location).body(response);
+ }
+
+ @PutMapping("/{id}")
+ ResponseEntity updateAnimal(
+ @PathVariable Long id, @RequestBody @Valid AnimalRequest animalRequest) {
+ return ResponseEntity.ok(animalService.updateAnimal(id, animalRequest));
+ }
+
+ @DeleteMapping("/{id}")
+ ResponseEntity deleteAnimal(@PathVariable Long id) {
+ return animalService
+ .findAnimalById(id)
+ .map(animal -> {
+ animalService.deleteAnimalById(id);
+ return ResponseEntity.ok(animal);
+ })
+ .orElseThrow(() -> new AnimalNotFoundException(id));
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application-local.properties b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application-local.properties
new file mode 100644
index 000000000..62c4e116a
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application-local.properties
@@ -0,0 +1,5 @@
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.url=jdbc:postgresql://localhost:5432/appdb
+spring.datasource.username=appuser
+spring.datasource.password=secret
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application.properties b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application.properties
new file mode 100644
index 000000000..65ac93b77
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/application.properties
@@ -0,0 +1,29 @@
+spring.application.name=boot-data-window-paginiation
+server.port=8080
+server.shutdown=graceful
+spring.main.allow-bean-definition-overriding=true
+spring.jmx.enabled=false
+spring.mvc.problemdetails.enabled=true
+
+################ Actuator #####################
+management.endpoints.web.exposure.include=configprops,env,health,info,logfile,loggers,metrics,prometheus
+management.endpoint.health.show-details=always
+
+################ Database #####################
+spring.jpa.show-sql=false
+spring.jpa.open-in-view=false
+spring.data.jpa.repositories.bootstrap-mode=deferred
+spring.datasource.hikari.auto-commit=false
+spring.datasource.hikari.data-source-properties.ApplicationName=${spring.application.name}
+spring.jpa.hibernate.ddl-auto=none
+#spring.jpa.properties.hibernate.format_sql=true
+spring.jpa.properties.hibernate.jdbc.time_zone=UTC
+spring.jpa.properties.hibernate.generate_statistics=false
+spring.jpa.properties.hibernate.jdbc.batch_size=25
+spring.jpa.properties.hibernate.order_inserts=true
+spring.jpa.properties.hibernate.order_updates=true
+spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true
+spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true
+spring.jpa.properties.hibernate.query.plan_cache_max_size=4096
+spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
+spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/db.changelog-master.yaml b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 000000000..7e9e64aa4
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1,5 @@
+databaseChangeLog:
+ - includeAll:
+ path: migration/
+ errorIfMissingOrEmpty: true
+ relativeToChangelogFile: true
\ No newline at end of file
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/01-init.xml b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/01-init.xml
new file mode 100644
index 000000000..9d3b8b9b3
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/01-init.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/02-create_animals_table.xml b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/02-create_animals_table.xml
new file mode 100644
index 000000000..d5d2b4925
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/db/changelog/migration/02-create_animals_table.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/logback-spring.xml b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/logback-spring.xml
new file mode 100644
index 000000000..fb95210fc
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/main/resources/logback-spring.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java
new file mode 100644
index 000000000..d85feafe1
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/ApplicationIntegrationTest.java
@@ -0,0 +1,10 @@
+package com.example.keysetpagination;
+
+import com.example.keysetpagination.common.AbstractIntegrationTest;
+import org.junit.jupiter.api.Test;
+
+class ApplicationIntegrationTest extends AbstractIntegrationTest {
+
+ @Test
+ void contextLoads() {}
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/SchemaValidationTest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/SchemaValidationTest.java
new file mode 100644
index 000000000..277aa50a6
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/SchemaValidationTest.java
@@ -0,0 +1,16 @@
+package com.example.keysetpagination;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+@DataJpaTest(
+ properties = {
+ "spring.jpa.hibernate.ddl-auto=validate",
+ "spring.test.database.replace=none",
+ "spring.datasource.url=jdbc:tc:postgresql:16.3-alpine:///db"
+ })
+class SchemaValidationTest {
+
+ @Test
+ void validateJpaMappingsWithDbSchema() {}
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/TestApplication.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/TestApplication.java
new file mode 100644
index 000000000..a77e888b0
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/TestApplication.java
@@ -0,0 +1,12 @@
+package com.example.keysetpagination;
+
+import com.example.keysetpagination.common.ContainersConfig;
+import org.springframework.boot.SpringApplication;
+
+public class TestApplication {
+
+ public static void main(String[] args) {
+ System.setProperty("spring.profiles.active", "local");
+ SpringApplication.from(Application::main).with(ContainersConfig.class).run(args);
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java
new file mode 100644
index 000000000..aa438d728
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/AbstractIntegrationTest.java
@@ -0,0 +1,25 @@
+package com.example.keysetpagination.common;
+
+import static com.example.keysetpagination.utils.AppConstants.PROFILE_TEST;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+
+@ActiveProfiles({PROFILE_TEST})
+@SpringBootTest(
+ webEnvironment = RANDOM_PORT,
+ classes = {ContainersConfig.class})
+@AutoConfigureMockMvc
+public abstract class AbstractIntegrationTest {
+
+ @Autowired
+ protected MockMvc mockMvc;
+
+ @Autowired
+ protected ObjectMapper objectMapper;
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/ContainersConfig.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/ContainersConfig.java
new file mode 100644
index 000000000..56b1ee388
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/common/ContainersConfig.java
@@ -0,0 +1,17 @@
+package com.example.keysetpagination.common;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.utility.DockerImageName;
+
+@TestConfiguration(proxyBeanMethods = false)
+public class ContainersConfig {
+
+ @Bean
+ @ServiceConnection
+ PostgreSQLContainer> postgreSQLContainer() {
+ return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.3-alpine"));
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/services/AnimalServiceTest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/services/AnimalServiceTest.java
new file mode 100644
index 000000000..c496a6175
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/services/AnimalServiceTest.java
@@ -0,0 +1,67 @@
+package com.example.keysetpagination.services;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.times;
+import static org.mockito.BDDMockito.verify;
+import static org.mockito.BDDMockito.willDoNothing;
+
+import com.example.keysetpagination.entities.Animal;
+import com.example.keysetpagination.mapper.AnimalMapper;
+import com.example.keysetpagination.model.response.AnimalResponse;
+import com.example.keysetpagination.repositories.AnimalRepository;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class AnimalServiceTest {
+
+ @Mock
+ private AnimalRepository animalRepository;
+
+ @Mock
+ private AnimalMapper animalMapper;
+
+ @InjectMocks
+ private AnimalService animalService;
+
+ @Test
+ void findAnimalById() {
+ // given
+ given(animalRepository.findById(1L)).willReturn(Optional.of(getAnimal()));
+ given(animalMapper.toResponse(any(Animal.class))).willReturn(getAnimalResponse());
+ // when
+ Optional optionalAnimal = animalService.findAnimalById(1L);
+ // then
+ assertThat(optionalAnimal).isPresent();
+ AnimalResponse animal = optionalAnimal.get();
+ assertThat(animal.id()).isEqualTo(1L);
+ assertThat(animal.text()).isEqualTo("junitTest");
+ }
+
+ @Test
+ void deleteAnimalById() {
+ // given
+ willDoNothing().given(animalRepository).deleteById(1L);
+ // when
+ animalService.deleteAnimalById(1L);
+ // then
+ verify(animalRepository, times(1)).deleteById(1L);
+ }
+
+ private Animal getAnimal() {
+ Animal animal = new Animal();
+ animal.setId(1L);
+ animal.setText("junitTest");
+ return animal;
+ }
+
+ private AnimalResponse getAnimalResponse() {
+ return new AnimalResponse(1L, "junitTest");
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java
new file mode 100644
index 000000000..27ea53162
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerIT.java
@@ -0,0 +1,129 @@
+package com.example.keysetpagination.web.controllers;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.hasSize;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.example.keysetpagination.common.AbstractIntegrationTest;
+import com.example.keysetpagination.entities.Animal;
+import com.example.keysetpagination.model.request.AnimalRequest;
+import com.example.keysetpagination.repositories.AnimalRepository;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+class AnimalControllerIT extends AbstractIntegrationTest {
+
+ @Autowired
+ private AnimalRepository animalRepository;
+
+ private List animalList = null;
+
+ @BeforeEach
+ void setUp() {
+ animalRepository.deleteAllInBatch();
+
+ animalList = new ArrayList<>();
+ animalList.add(new Animal(null, "First Animal"));
+ animalList.add(new Animal(null, "Second Animal"));
+ animalList.add(new Animal(null, "Third Animal"));
+ animalList = animalRepository.saveAll(animalList);
+ }
+
+ @Test
+ void shouldFetchAllAnimals() throws Exception {
+ this.mockMvc
+ .perform(get("/api/animals"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.size()", is(animalList.size())))
+ .andExpect(jsonPath("$.totalElements", is(3)))
+ .andExpect(jsonPath("$.pageNumber", is(1)))
+ .andExpect(jsonPath("$.totalPages", is(1)))
+ .andExpect(jsonPath("$.isFirst", is(true)))
+ .andExpect(jsonPath("$.isLast", is(true)))
+ .andExpect(jsonPath("$.hasNext", is(false)))
+ .andExpect(jsonPath("$.hasPrevious", is(false)));
+ }
+
+ @Test
+ void shouldFindAnimalById() throws Exception {
+ Animal animal = animalList.get(0);
+ Long animalId = animal.getId();
+
+ this.mockMvc
+ .perform(get("/api/animals/{id}", animalId))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(animal.getId()), Long.class))
+ .andExpect(jsonPath("$.text", is(animal.getText())));
+ }
+
+ @Test
+ void shouldCreateNewAnimal() throws Exception {
+ AnimalRequest animalRequest = new AnimalRequest("New Animal");
+ this.mockMvc
+ .perform(post("/api/animals")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists(HttpHeaders.LOCATION))
+ .andExpect(jsonPath("$.id", notNullValue()))
+ .andExpect(jsonPath("$.text", is(animalRequest.text())));
+ }
+
+ @Test
+ void shouldReturn400WhenCreateNewAnimalWithoutText() throws Exception {
+ AnimalRequest animalRequest = new AnimalRequest(null);
+
+ this.mockMvc
+ .perform(post("/api/animals")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isBadRequest())
+ .andExpect(header().string("Content-Type", is("application/problem+json")))
+ .andExpect(jsonPath("$.type", is("about:blank")))
+ .andExpect(jsonPath("$.title", is("Constraint Violation")))
+ .andExpect(jsonPath("$.status", is(400)))
+ .andExpect(jsonPath("$.detail", is("Invalid request content.")))
+ .andExpect(jsonPath("$.instance", is("/api/animals")))
+ .andExpect(jsonPath("$.violations", hasSize(1)))
+ .andExpect(jsonPath("$.violations[0].field", is("text")))
+ .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty")))
+ .andReturn();
+ }
+
+ @Test
+ void shouldUpdateAnimal() throws Exception {
+ Long animalId = animalList.get(0).getId();
+ AnimalRequest animalRequest = new AnimalRequest("Updated Animal");
+
+ this.mockMvc
+ .perform(put("/api/animals/{id}", animalId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(animalId), Long.class))
+ .andExpect(jsonPath("$.text", is(animalRequest.text())));
+ }
+
+ @Test
+ void shouldDeleteAnimal() throws Exception {
+ Animal animal = animalList.get(0);
+
+ this.mockMvc
+ .perform(delete("/api/animals/{id}", animal.getId()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(animal.getId()), Long.class))
+ .andExpect(jsonPath("$.text", is(animal.getText())));
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerTest.java b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerTest.java
new file mode 100644
index 000000000..06e51fb00
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/java/com/example/keysetpagination/web/controllers/AnimalControllerTest.java
@@ -0,0 +1,219 @@
+package com.example.keysetpagination.web.controllers;
+
+import static com.example.keysetpagination.utils.AppConstants.PROFILE_TEST;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doNothing;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.example.keysetpagination.entities.Animal;
+import com.example.keysetpagination.exception.AnimalNotFoundException;
+import com.example.keysetpagination.model.query.FindAnimalsQuery;
+import com.example.keysetpagination.model.request.AnimalRequest;
+import com.example.keysetpagination.model.response.AnimalResponse;
+import com.example.keysetpagination.model.response.PagedResult;
+import com.example.keysetpagination.services.AnimalService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+
+@WebMvcTest(controllers = AnimalController.class)
+@ActiveProfiles(PROFILE_TEST)
+class AnimalControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private AnimalService animalService;
+
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ private List animalList;
+
+ @BeforeEach
+ void setUp() {
+ this.animalList = new ArrayList<>();
+ this.animalList.add(new Animal(1L, "text 1"));
+ this.animalList.add(new Animal(2L, "text 2"));
+ this.animalList.add(new Animal(3L, "text 3"));
+ }
+
+ @Test
+ void shouldFetchAllAnimals() throws Exception {
+
+ Page page = new PageImpl<>(animalList);
+ PagedResult animalPagedResult = new PagedResult<>(page, getAnimalResponseList());
+ FindAnimalsQuery findAnimalsQuery = new FindAnimalsQuery(0, 10, "id", "asc");
+ given(animalService.findAllAnimals(findAnimalsQuery)).willReturn(animalPagedResult);
+
+ this.mockMvc
+ .perform(get("/api/animals"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.data.size()", is(animalList.size())))
+ .andExpect(jsonPath("$.totalElements", is(3)))
+ .andExpect(jsonPath("$.pageNumber", is(1)))
+ .andExpect(jsonPath("$.totalPages", is(1)))
+ .andExpect(jsonPath("$.isFirst", is(true)))
+ .andExpect(jsonPath("$.isLast", is(true)))
+ .andExpect(jsonPath("$.hasNext", is(false)))
+ .andExpect(jsonPath("$.hasPrevious", is(false)));
+ }
+
+ @Test
+ void shouldFindAnimalById() throws Exception {
+ Long animalId = 1L;
+ AnimalResponse animal = new AnimalResponse(animalId, "text 1");
+ given(animalService.findAnimalById(animalId)).willReturn(Optional.of(animal));
+
+ this.mockMvc
+ .perform(get("/api/animals/{id}", animalId))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.text", is(animal.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenFetchingNonExistingAnimal() throws Exception {
+ Long animalId = 1L;
+ given(animalService.findAnimalById(animalId)).willReturn(Optional.empty());
+
+ this.mockMvc
+ .perform(get("/api/animals/{id}", animalId))
+ .andExpect(status().isNotFound())
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.boot-data-window-paginiation.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(jsonPath("$.detail").value("Animal with Id '%d' not found".formatted(animalId)));
+ }
+
+ @Test
+ void shouldCreateNewAnimal() throws Exception {
+
+ AnimalResponse animal = new AnimalResponse(1L, "some text");
+ AnimalRequest animalRequest = new AnimalRequest("some text");
+ given(animalService.saveAnimal(any(AnimalRequest.class))).willReturn(animal);
+
+ this.mockMvc
+ .perform(post("/api/animals")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists(HttpHeaders.LOCATION))
+ .andExpect(jsonPath("$.id", notNullValue()))
+ .andExpect(jsonPath("$.text", is(animal.text())));
+ }
+
+ @Test
+ void shouldReturn400WhenCreateNewAnimalWithoutText() throws Exception {
+ AnimalRequest animalRequest = new AnimalRequest(null);
+
+ this.mockMvc
+ .perform(post("/api/animals")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isBadRequest())
+ .andExpect(header().string("Content-Type", is("application/problem+json")))
+ .andExpect(jsonPath("$.type", is("about:blank")))
+ .andExpect(jsonPath("$.title", is("Constraint Violation")))
+ .andExpect(jsonPath("$.status", is(400)))
+ .andExpect(jsonPath("$.detail", is("Invalid request content.")))
+ .andExpect(jsonPath("$.instance", is("/api/animals")))
+ .andExpect(jsonPath("$.violations", hasSize(1)))
+ .andExpect(jsonPath("$.violations[0].field", is("text")))
+ .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty")))
+ .andReturn();
+ }
+
+ @Test
+ void shouldUpdateAnimal() throws Exception {
+ Long animalId = 1L;
+ AnimalResponse animal = new AnimalResponse(animalId, "Updated text");
+ AnimalRequest animalRequest = new AnimalRequest("Updated text");
+ given(animalService.updateAnimal(eq(animalId), any(AnimalRequest.class)))
+ .willReturn(animal);
+
+ this.mockMvc
+ .perform(put("/api/animals/{id}", animalId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id", is(animalId), Long.class))
+ .andExpect(jsonPath("$.text", is(animal.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenUpdatingNonExistingAnimal() throws Exception {
+ Long animalId = 1L;
+ AnimalRequest animalRequest = new AnimalRequest("Updated text");
+ given(animalService.updateAnimal(eq(animalId), any(AnimalRequest.class)))
+ .willThrow(new AnimalNotFoundException(animalId));
+
+ this.mockMvc
+ .perform(put("/api/animals/{id}", animalId)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(animalRequest)))
+ .andExpect(status().isNotFound())
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.boot-data-window-paginiation.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(jsonPath("$.detail").value("Animal with Id '%d' not found".formatted(animalId)));
+ }
+
+ @Test
+ void shouldDeleteAnimal() throws Exception {
+ Long animalId = 1L;
+ AnimalResponse animal = new AnimalResponse(animalId, "Some text");
+ given(animalService.findAnimalById(animalId)).willReturn(Optional.of(animal));
+ doNothing().when(animalService).deleteAnimalById(animalId);
+
+ this.mockMvc
+ .perform(delete("/api/animals/{id}", animalId))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.text", is(animal.text())));
+ }
+
+ @Test
+ void shouldReturn404WhenDeletingNonExistingAnimal() throws Exception {
+ Long animalId = 1L;
+ given(animalService.findAnimalById(animalId)).willReturn(Optional.empty());
+
+ this.mockMvc
+ .perform(delete("/api/animals/{id}", animalId))
+ .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE)))
+ .andExpect(jsonPath("$.type", is("http://api.boot-data-window-paginiation.com/errors/not-found")))
+ .andExpect(jsonPath("$.title", is("Not Found")))
+ .andExpect(jsonPath("$.status", is(404)))
+ .andExpect(jsonPath("$.detail").value("Animal with Id '%d' not found".formatted(animalId)));
+ }
+
+ List getAnimalResponseList() {
+ return animalList.stream()
+ .map(animal -> new AnimalResponse(animal.getId(), animal.getText()))
+ .toList();
+ }
+}
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/resources/application-test.properties b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/resources/application-test.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/jpa/keyset-pagination/boot-data-window-paginiation/src/test/resources/logback-test.xml b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..1378a823a
--- /dev/null
+++ b/jpa/keyset-pagination/boot-data-window-paginiation/src/test/resources/logback-test.xml
@@ -0,0 +1,14 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
+
+
+
+
+
+
+
+
+
+