diff --git a/persistence-modules/spring-boot-persistence-4/pom.xml b/persistence-modules/spring-boot-persistence-4/pom.xml
new file mode 100644
index 0000000000..99c39e205d
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+ com.baeldung.boot.persistence
+ spring-boot-persistence-4
+ 0.0.1-SNAPSHOT
+ spring-boot-persistence-4
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit-jupiter.version}
+ pom
+ import
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.dependencies}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+ 3.1.0
+ 5.9.3
+ 17
+ 17
+
+
+
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/ScrollAPIApplication.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/ScrollAPIApplication.java
new file mode 100644
index 0000000000..27e6555e26
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/ScrollAPIApplication.java
@@ -0,0 +1,11 @@
+package com.baeldung.scrollapi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ScrollAPIApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ScrollAPIApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/entity/BookReview.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/entity/BookReview.java
new file mode 100644
index 0000000000..4baa2cda83
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/entity/BookReview.java
@@ -0,0 +1,53 @@
+package com.baeldung.scrollapi.entity;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "BOOK_REVIEWS")
+public class BookReview {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_reviews_reviews_id_seq")
+ @SequenceGenerator(name = "book_reviews_reviews_id_seq", sequenceName = "book_reviews_reviews_id_seq", allocationSize = 1)
+ private Long reviewsId;
+ private String userId;
+ private String isbn;
+ private String bookRating;
+
+ public Long getReviewsId() {
+ return reviewsId;
+ }
+
+ public void setReviewsId(Long reviewsId) {
+ this.reviewsId = reviewsId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIsbn() {
+ return isbn;
+ }
+
+ public void setIsbn(String isbn) {
+ this.isbn = isbn;
+ }
+
+ public String getBookRating() {
+ return bookRating;
+ }
+
+ public void setBookRating(String bookRating) {
+ this.bookRating = bookRating;
+ }
+
+ @Override
+ public String toString() {
+ return "BookReview{" + "reviewsId=" + reviewsId + ", userId='" + userId + '\'' + ", isbn='" + isbn + '\'' + ", bookRating='" + bookRating + '\'' + '}';
+ }
+}
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/repository/BookRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/repository/BookRepository.java
new file mode 100644
index 0000000000..717cbc31c7
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/repository/BookRepository.java
@@ -0,0 +1,16 @@
+package com.baeldung.scrollapi.repository;
+
+import org.springframework.data.domain.KeysetScrollPosition;
+import org.springframework.data.domain.OffsetScrollPosition;
+import org.springframework.data.domain.Window;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.Repository;
+
+import com.baeldung.scrollapi.entity.BookReview;
+
+public interface BookRepository extends JpaRepository {
+
+ Window findFirst5ByBookRating(String bookRating, OffsetScrollPosition position);
+
+ Window findFirst5ByBookRating(String bookRating, KeysetScrollPosition position);
+}
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/service/BookLogic.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/service/BookLogic.java
new file mode 100644
index 0000000000..aacf0e3a78
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/scrollapi/service/BookLogic.java
@@ -0,0 +1,53 @@
+package com.baeldung.scrollapi.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.KeysetScrollPosition;
+import org.springframework.data.domain.OffsetScrollPosition;
+import org.springframework.data.domain.ScrollPosition;
+import org.springframework.data.domain.Window;
+import org.springframework.data.support.WindowIterator;
+import org.springframework.stereotype.Service;
+
+import com.baeldung.scrollapi.entity.BookReview;
+import com.baeldung.scrollapi.repository.BookRepository;
+
+@Service
+public class BookLogic {
+
+ @Autowired
+ private BookRepository bookRepository;
+
+ public List getBooksUsingOffset(String rating) {
+ OffsetScrollPosition offset = ScrollPosition.offset();
+
+ Window bookReviews = bookRepository.findFirst5ByBookRating(rating, offset);
+ List bookReviewsResult = new ArrayList<>();
+ do {
+ bookReviews.forEach(bookReviewsResult::add);
+ bookReviews = bookRepository.findFirst5ByBookRating(rating, (OffsetScrollPosition) bookReviews.positionAt(bookReviews.size() - 1));
+ } while (!bookReviews.isEmpty() && bookReviews.hasNext());
+
+ return bookReviewsResult;
+ }
+
+ public List getBooksUsingOffSetFilteringAndWindowIterator(String rating) {
+ WindowIterator bookReviews = WindowIterator.of(position -> bookRepository.findFirst5ByBookRating("3.5", (OffsetScrollPosition) position))
+ .startingAt(ScrollPosition.offset());
+ List bookReviewsResult = new ArrayList<>();
+
+ bookReviews.forEachRemaining(bookReviewsResult::add);
+ return bookReviewsResult;
+ }
+
+ public List getBooksUsingKeySetFiltering(String rating) {
+ WindowIterator bookReviews = WindowIterator.of(position -> bookRepository.findFirst5ByBookRating(rating, (KeysetScrollPosition) position))
+ .startingAt(ScrollPosition.keyset());
+ List bookReviewsResult = new ArrayList<>();
+
+ bookReviews.forEachRemaining(bookReviewsResult::add);
+ return bookReviewsResult;
+ }
+}
diff --git a/persistence-modules/spring-boot-persistence-4/src/main/resources/application.yml b/persistence-modules/spring-boot-persistence-4/src/main/resources/application.yml
new file mode 100644
index 0000000000..bb9e377c34
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/main/resources/application.yml
@@ -0,0 +1,4 @@
+
+logging.level.org.hibernate:
+ SQL: DEBUG
+ type.descriptor.sql.BasicBinder: TRACE
\ No newline at end of file
diff --git a/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/boot/scrollapi/service/BookLogicUnitTest.java b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/boot/scrollapi/service/BookLogicUnitTest.java
new file mode 100644
index 0000000000..be816e62cf
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/boot/scrollapi/service/BookLogicUnitTest.java
@@ -0,0 +1,73 @@
+package com.baeldung.boot.scrollapi.service;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import com.baeldung.scrollapi.ScrollAPIApplication;
+import com.baeldung.scrollapi.entity.BookReview;
+import com.baeldung.scrollapi.repository.BookRepository;
+import com.baeldung.scrollapi.service.BookLogic;
+
+@SpringBootTest(classes = ScrollAPIApplication.class)
+class BookLogicUnitTest {
+
+ @Autowired
+ private BookRepository bookRepository;
+
+ @Autowired
+ private BookLogic bookLogic;
+
+ @BeforeEach
+ public void beforeEach() {
+ IntStream.rangeClosed(1, 5)
+ .forEach(i -> insertBookReview());
+ }
+
+ @AfterEach
+ public void afterEach() {
+ bookRepository.deleteAll();
+ }
+
+ @Test
+ public void givenBookReviewInTable_whenGetBooksUsingOffset_returnsBookReviews() {
+ List bookReviews = bookLogic.getBooksUsingOffset("3.5");
+ assertThat(bookReviews.size()).isEqualTo(5);
+ }
+
+ @Test
+ public void givenBookReviewInTable_whenGetBooksUsingOffSetFilteringAndWindowIterator_returnsBookReviews() {
+ List bookReviews = bookLogic.getBooksUsingOffSetFilteringAndWindowIterator("3.5");
+ assertThat(bookReviews.size()).isEqualTo(5);
+ }
+
+ @Test
+ public void givenBookReviewInTable_whenGetBooksUsingKeySetFiltering_returnsBookReviews() {
+ List bookReviews = bookLogic.getBooksUsingKeySetFiltering("3.5");
+ assertThat(bookReviews.size()).isEqualTo(5);
+ }
+
+ private void insertBookReview() {
+ BookReview bookReview = getBookReview();
+ bookRepository.save(bookReview);
+ }
+
+ private static BookReview getBookReview() {
+ BookReview bookReview = new BookReview();
+ String seed = UUID.randomUUID()
+ .toString();
+ bookReview.setIsbn("isbn" + seed);
+ bookReview.setBookRating("3.5");
+ bookReview.setUserId(seed);
+
+ return bookReview;
+ }
+}
diff --git a/persistence-modules/spring-boot-persistence-4/src/test/resources/application.properties b/persistence-modules/spring-boot-persistence-4/src/test/resources/application.properties
new file mode 100644
index 0000000000..a21dcc731e
--- /dev/null
+++ b/persistence-modules/spring-boot-persistence-4/src/test/resources/application.properties
@@ -0,0 +1,5 @@
+# spring.datasource.x
+spring.datasource.driver-class-name=org.h2.Driver
+spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
+spring.datasource.username=sa
+spring.datasource.password=sa
\ No newline at end of file