From 7055bb3f6b190855e27f792e69efd479bd0303cf Mon Sep 17 00:00:00 2001 From: Manfred <77407079+manfred106@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:52:45 +0000 Subject: [PATCH] BAEL-7282: Getting all results at once in a paged query method (#15761) --- .../spring-boot-persistence-4/pom.xml | 13 +++ .../baeldung/paging/PagingApplication.java | 13 +++ .../main/java/com/baeldung/paging/School.java | 31 ++++++ .../java/com/baeldung/paging/Student.java | 42 +++++++ .../paging/StudentCustomQueryRepository.java | 14 +++ .../java/com/baeldung/paging/StudentDTO.java | 22 ++++ .../paging/StudentEntityGraphRepository.java | 13 +++ .../StudentNamedEntityGraphRepository.java | 13 +++ .../baeldung/paging/StudentRepository.java | 6 + .../paging/StudentWithSchoolNameDTO.java | 24 ++++ .../StudentRepositoryIntegrationTest.java | 103 ++++++++++++++++++ .../test/resources/school-student-data.sql | 9 ++ 12 files changed, 303 insertions(+) create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/PagingApplication.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/School.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/Student.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentCustomQueryRepository.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentDTO.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentEntityGraphRepository.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentNamedEntityGraphRepository.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentRepository.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentWithSchoolNameDTO.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/paging/StudentRepositoryIntegrationTest.java create mode 100644 persistence-modules/spring-boot-persistence-4/src/test/resources/school-student-data.sql diff --git a/persistence-modules/spring-boot-persistence-4/pom.xml b/persistence-modules/spring-boot-persistence-4/pom.xml index 223b754f07..13cb4d5b82 100644 --- a/persistence-modules/spring-boot-persistence-4/pom.xml +++ b/persistence-modules/spring-boot-persistence-4/pom.xml @@ -57,6 +57,17 @@ jackson-databind ${jackson.version} + + org.modelmapper + modelmapper + ${modelmapper.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + @@ -76,6 +87,8 @@ 1.0.7 3.7.0 2.16.0 + 3.2.0 + 1.18.30 \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/PagingApplication.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/PagingApplication.java new file mode 100644 index 0000000000..7a38ec5cd8 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/PagingApplication.java @@ -0,0 +1,13 @@ +package com.baeldung.paging; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PagingApplication { + + public static void main(String[] args) { + SpringApplication.run(PagingApplication.class, args); + } + +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/School.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/School.java new file mode 100644 index 0000000000..6c02cbe6dd --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/School.java @@ -0,0 +1,31 @@ +package com.baeldung.paging; + +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 lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "school") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = {"id"}) +public class School { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "school_id") + private Integer id; + + private String name; + +} diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/Student.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/Student.java new file mode 100644 index 0000000000..5cc20cd9af --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/Student.java @@ -0,0 +1,42 @@ +package com.baeldung.paging; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "student") +@NamedEntityGraph(name = "Student.school", attributeNodes = @NamedAttributeNode("school")) +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = {"id"}) +public class Student { + + @Id + @Column(name = "student_id") + private String id; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "school_id", referencedColumnName = "school_id") + private School school; + +} diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentCustomQueryRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentCustomQueryRepository.java new file mode 100644 index 0000000000..591c4a9157 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentCustomQueryRepository.java @@ -0,0 +1,14 @@ +package com.baeldung.paging; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface StudentCustomQueryRepository extends JpaRepository { + + @Query(value = "SELECT stu FROM Student stu LEFT JOIN FETCH stu.school ", + countQuery = "SELECT COUNT(stu) FROM Student stu") + Page findAll(Pageable pageable); + +} diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentDTO.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentDTO.java new file mode 100644 index 0000000000..b4fc068e68 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentDTO.java @@ -0,0 +1,22 @@ +package com.baeldung.paging; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = {"id"}) +public class StudentDTO { + + private String id; + + private String firstName; + + private String lastName; + +} diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentEntityGraphRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentEntityGraphRepository.java new file mode 100644 index 0000000000..b63dcbbb8f --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentEntityGraphRepository.java @@ -0,0 +1,13 @@ +package com.baeldung.paging; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StudentEntityGraphRepository extends JpaRepository { + + @EntityGraph(attributePaths = "school") + Page findAll(Pageable pageable); + +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentNamedEntityGraphRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentNamedEntityGraphRepository.java new file mode 100644 index 0000000000..c901da3c30 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentNamedEntityGraphRepository.java @@ -0,0 +1,13 @@ +package com.baeldung.paging; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StudentNamedEntityGraphRepository extends JpaRepository { + + @EntityGraph(value = "Student.school") + Page findAll(Pageable pageable); + +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentRepository.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentRepository.java new file mode 100644 index 0000000000..e1bb78123c --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentRepository.java @@ -0,0 +1,6 @@ +package com.baeldung.paging; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StudentRepository extends JpaRepository { +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentWithSchoolNameDTO.java b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentWithSchoolNameDTO.java new file mode 100644 index 0000000000..ef634f78ac --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/main/java/com/baeldung/paging/StudentWithSchoolNameDTO.java @@ -0,0 +1,24 @@ +package com.baeldung.paging; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = {"id"}) +public class StudentWithSchoolNameDTO { + + private String id; + + private String firstName; + + private String lastName; + + private String schoolName; + +} diff --git a/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/paging/StudentRepositoryIntegrationTest.java b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/paging/StudentRepositoryIntegrationTest.java new file mode 100644 index 0000000000..dbc1d5edb9 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/test/java/com/baeldung/paging/StudentRepositoryIntegrationTest.java @@ -0,0 +1,103 @@ +package com.baeldung.paging; + +import com.baeldung.listvsset.util.TestConfig; +import io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +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.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; + +import static io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator.assertSelectCount; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@SpringBootTest(classes = {PagingApplication.class, TestConfig.class}, properties = { + "spring.jpa.show-sql=true", + "spring.jpa.properties.hibernate.format_sql=true", + "spring.jpa.generate-ddl=true", + "spring.jpa.defer-datasource-initialization=true", + "spring.sql.init.data-locations=classpath:school-student-data.sql" +}) +@Transactional +class StudentRepositoryIntegrationTest { + + @Autowired + private StudentRepository studentRepository; + + @Autowired + private StudentCustomQueryRepository studentCustomQueryRepository; + + @Autowired + private StudentEntityGraphRepository studentEntityGraphRepository; + + @Autowired + private StudentNamedEntityGraphRepository studentNamedEntityGraphRepository; + + private static final ModelMapper modelMapper = new ModelMapper(); + + @BeforeEach + void setUp() { + SQLStatementCountValidator.reset(); + } + + @Test + public void whenGetStudentsWithPageRequestOfTwo_thenReturnTwoRows() { + int rows = 2; + Pageable pageable = PageRequest.of(0, rows); + Page studentPage = studentRepository.findAll(pageable); + + // Then + List studentList = studentPage.getContent(); + assertThat(studentList.size()).isEqualTo(rows); + } + + @Test + public void whenGetStudentsWithUnpagedAndSort_thenReturnAllResultsSorted() { + Sort sort = Sort.by(Sort.Direction.ASC, "lastName"); + Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE).withSort(sort); + Page studentPage = studentRepository.findAll(pageable); + + // Then + List studentList = studentPage.getContent(); + assertThat(studentList.size()).isEqualTo(studentPage.getTotalElements()); + assertThat(studentList).isSortedAccordingTo(Comparator.comparing(Student::getLastName)); + } + + + @Test + public void whenGetStudentsWithSchool_thenMultipleSelectQueriesAreExecuted() { + Page studentPage = studentRepository.findAll(Pageable.unpaged()); + studentPage.get().map(student -> modelMapper.map(student, StudentWithSchoolNameDTO.class)).toList(); + assertSelectCount(studentPage.getContent().size() + 1); + } + + @Test + public void whenGetStudentsByCustomQuery_thenOneSelectQueryIsExecuted() { + Page studentPage = studentCustomQueryRepository.findAll(Pageable.unpaged()); + studentPage.get().map(student -> modelMapper.map(student, StudentWithSchoolNameDTO.class)).toList(); + assertSelectCount(1); + } + + @Test + public void whenGetStudentsByEntityGraph_thenOneSelectQueryIsExecuted() { + Page studentPage = studentEntityGraphRepository.findAll(Pageable.unpaged()); + studentPage.get().map(student -> modelMapper.map(student, StudentWithSchoolNameDTO.class)).toList(); + assertSelectCount(1); + } + + @Test + public void whenGetStudentsByNamedEntityGraph_thenOneSelectQueryIsExecuted() { + Page studentPage = studentNamedEntityGraphRepository.findAll(Pageable.unpaged()); + studentPage.get().map(student -> modelMapper.map(student, StudentWithSchoolNameDTO.class)).toList(); + assertSelectCount(1); + } + +} \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-4/src/test/resources/school-student-data.sql b/persistence-modules/spring-boot-persistence-4/src/test/resources/school-student-data.sql new file mode 100644 index 0000000000..de9699d256 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-4/src/test/resources/school-student-data.sql @@ -0,0 +1,9 @@ +INSERT INTO school (name) VALUES ('Ada Lovelace CE High School'); +INSERT INTO school (name) VALUES ('Ealing Fields High School'); +INSERT INTO school (name) VALUES ('Northolt High School'); +INSERT INTO school (name) VALUES ('Villiers High School'); + +INSERT INTO student(student_id, first_name, last_name, school_id) VALUES('23056746', 'James', 'Drover', 1); +INSERT INTO student(student_id, first_name, last_name, school_id) VALUES('23056751', 'Rubin', 'Webber', 2); +INSERT INTO student(student_id, first_name, last_name, school_id) VALUES('23063444', 'Sarah', 'Pelham', 3); +INSERT INTO student(student_id, first_name, last_name, school_id) VALUES('23065783', 'Lucy', 'Watson', 4); \ No newline at end of file