diff --git a/spring-boot-modules/spring-boot-data-3/pom.xml b/spring-boot-modules/spring-boot-data-3/pom.xml index c10ad60223c2..f134acd832af 100644 --- a/spring-boot-modules/spring-boot-data-3/pom.xml +++ b/spring-boot-modules/spring-boot-data-3/pom.xml @@ -43,6 +43,19 @@ spring-boot-starter-test test + + com.querydsl + querydsl-jpa + ${querydsl.version} + jakarta + + + com.querydsl + querydsl-apt + ${querydsl.version} + jakarta + provided + org.openjfx javafx-controls @@ -53,6 +66,12 @@ javafx-fxml ${javafx.version} + + org.projectlombok + lombok + ${lombok.version} + compile + @@ -61,12 +80,38 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + generate-sources + + process + + + target/generated-sources + com.mysema.query.apt.jpa.JPAAnnotationProcessor + + + + com.baeldung.startwithoutdb.StartWithoutDbApplication 3.7.3 + 5.1.0 19 diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/DynamicQueryApplication.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/DynamicQueryApplication.java new file mode 100644 index 000000000000..c39165b11506 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/DynamicQueryApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.dynamicquery; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableJpaRepositories("com.baeldung.dynamicquery.repository") +public class DynamicQueryApplication { + + public static void main(String[] args) { + SpringApplication.run(DynamicQueryApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/School.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/School.java new file mode 100644 index 000000000000..79270f369a89 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/School.java @@ -0,0 +1,43 @@ +package com.baeldung.dynamicquery.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Entity +@Table +@Getter +@Setter +@EqualsAndHashCode(of = "id") +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class School { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column + private Long id; + + @Column + private String name; + + @Column + private String borough; + + @OneToMany(mappedBy = "school") + private List studentList; + +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/Student.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/Student.java new file mode 100644 index 000000000000..b24baad7bde9 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/model/Student.java @@ -0,0 +1,42 @@ +package com.baeldung.dynamicquery.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table +@Getter +@Setter +@EqualsAndHashCode(of = "id") +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Student { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column + private Long id; + + @Column + private String name; + + @Column + private Integer age; + + @ManyToOne + private School school; + +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/SchoolRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/SchoolRepository.java new file mode 100644 index 000000000000..63b3941d43f5 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/SchoolRepository.java @@ -0,0 +1,7 @@ +package com.baeldung.dynamicquery.repository; + +import com.baeldung.dynamicquery.model.School; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SchoolRepository extends JpaRepository { +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/StudentRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/StudentRepository.java new file mode 100644 index 000000000000..231a3fb20f1d --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/repository/StudentRepository.java @@ -0,0 +1,9 @@ +package com.baeldung.dynamicquery.repository; + +import com.baeldung.dynamicquery.model.Student; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; + +public interface StudentRepository extends JpaRepository, QuerydslPredicateExecutor, JpaSpecificationExecutor { +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/spec/StudentSpecification.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/spec/StudentSpecification.java new file mode 100644 index 000000000000..951da24f3b94 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/dynamicquery/spec/StudentSpecification.java @@ -0,0 +1,26 @@ +package com.baeldung.dynamicquery.spec; + +import com.baeldung.dynamicquery.model.School; +import com.baeldung.dynamicquery.model.Student; +import jakarta.persistence.criteria.Join; +import org.springframework.data.jpa.domain.Specification; + +public class StudentSpecification { + + public static Specification nameEndsWithIgnoreCase(String name) { + return (root, query, criteriaBuilder) -> + criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase()); + } + + public static Specification isAge(int age) { + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("age"), age); + } + + public static Specification isSchoolBorough(String borough) { + return (root, query, criteriaBuilder) -> { + Join scchoolJoin = root.join("school"); + return criteriaBuilder.equal(scchoolJoin.get("borough"), borough); + }; + } + +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/resources/application-dynamicquery.properties b/spring-boot-modules/spring-boot-data-3/src/main/resources/application-dynamicquery.properties new file mode 100644 index 000000000000..e138d41d2323 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/resources/application-dynamicquery.properties @@ -0,0 +1,9 @@ +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.h2.console.enabled=true + +# JPA Configuration +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=update \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/dynamicquery/DynamicQueryIntegrationTest.java b/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/dynamicquery/DynamicQueryIntegrationTest.java new file mode 100644 index 000000000000..a62d3b202419 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/dynamicquery/DynamicQueryIntegrationTest.java @@ -0,0 +1,95 @@ +package com.baeldung.dynamicquery; + +import com.baeldung.dynamicquery.model.QStudent; +import com.baeldung.dynamicquery.model.School; +import com.baeldung.dynamicquery.model.Student; +import com.baeldung.dynamicquery.repository.SchoolRepository; +import com.baeldung.dynamicquery.repository.StudentRepository; +import com.baeldung.dynamicquery.spec.StudentSpecification; +import com.querydsl.core.types.dsl.*; +import jakarta.inject.Inject; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@ActiveProfiles("dynamicquery") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class DynamicQueryIntegrationTest { + + @Inject + private SchoolRepository schoolRepository; + + @Inject + private StudentRepository studentRepository; + + @BeforeAll + void setup() { + School school1 = schoolRepository.save(School.builder() + .name("University of West London") + .borough("Ealing") + .build()); + + School school2 = schoolRepository.save(School.builder() + .name("Kingston University") + .borough("Kingston upon Thames") + .build()); + + studentRepository.saveAll(List.of( + Student.builder().name("Emily Smith").age(20).school(school2).build(), + Student.builder().name("James Smith").age(20).school(school1).build(), + Student.builder().name("Maria Johnson").age(22).school(school1).build(), + Student.builder().name("Michael Brown").age(21).school(school1).build(), + Student.builder().name("Sophia Smith").age(22).school(school1).build() + )); + } + + @Test + void givenQueryByExample_whenSelectExample_thenReturnStudentsWhoAreAge20() { + School schoolExample = new School(); + schoolExample.setBorough("Ealing"); + + Student studentExample = new Student(); + studentExample.setAge(20); + studentExample.setName("Smith"); + studentExample.setSchool(schoolExample); + + ExampleMatcher customExampleMatcher = ExampleMatcher.matching() + .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.endsWith().ignoreCase()); + + Example example = Example.of(studentExample, customExampleMatcher); + List studentList = studentRepository.findAll(example); + assertThat(studentList).hasSize(1); + } + + @Test + void givenQueryBySpecification_whenSelectByDsl_thenReturn() { + Specification studentSpec = Specification + .where(StudentSpecification.nameEndsWithIgnoreCase("smith")) + .and(StudentSpecification.isAge(20)) + .and(StudentSpecification.isSchoolBorough("Ealing")); + + List studentList = studentRepository.findAll(studentSpec); + assertThat(studentList).hasSize(1); + } + + @Test + void givenQueryByQueryDsl_whenSelectByDsl_thenReturn() { + QStudent qStudent = QStudent.student; + BooleanExpression predicate = qStudent.name.endsWithIgnoreCase("smith") + .and(qStudent.age.eq(20)) + .and(qStudent.school.borough.eq("Ealing")); + + List studentList = (List) studentRepository.findAll(predicate); + assertThat(studentList).hasSize(1); + } + +} \ No newline at end of file