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