使用任意 AND 子句的動態 Spring Data JPA 儲存庫查詢
1. 概述
在使用 Spring Data 開發應用程式時,我們經常需要根據選擇標準建立動態查詢以從資料庫中取得資料。
本教學探討了在 Spring Data JPA 儲存庫中建立動態查詢的三種方法:透過Example查詢、透過Specification,查詢和透過 Querydsl 查詢。
2. 場景設定
在我們的演示中,我們將建立兩個實體School和Student 。這些班級之間的關聯是一對多的,其中一所School可以有許多Student :
@Entity
@Table
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<Student> studentList;
// constructor, getters and setters
}
@Entity
@Table
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private Long id;
@Column
private String name;
@Column
private Integer age;
@ManyToOne
private School school;
// constructor, getters and setters
}
除了實體類別之外,我們也為Student實體定義一個 Spring Data 儲存庫:
public interface StudentRepository extends JpaRepository<Student, Long> {
}
最後,我們將在School表中添加一些範例資料:
| ID | 姓名 | 行政區 |
|---|---|---|
| 1 | 西倫敦大學 | 伊靈 |
| 2 | 金斯頓大學 | 泰晤士河畔金斯頓 |
讓我們對Student表也做同樣的事情:
| ID | 姓名 | 年齡 | 學校ID |
|---|---|---|---|
| 1 | 艾蜜莉史密斯 | 20 | 2 |
| 2 | 詹姆斯史密斯 | 20 | 1 |
| 3 | 瑪麗亞·約翰遜 | 22 | 1 |
| 4 | 麥可布朗 | 21 | 1 |
| 5 | 索菲亞·史密斯 | 22 | 1 |
在後續部分中,我們將透過以下標準使用不同的方法來尋找記錄:
-
Student的name以Smith結尾,並且 -
Studentage為20,且 - 該
Student的學校位於Ealing區
3. Example查詢
Spring Data 提供了一種使用範例查詢實體的簡單方法。這個想法很簡單:我們建立一個範例實體並將我們要尋找的搜尋條件放入其中。然後,我們使用這個範例來尋找與其相符的實體。
要採用這一點,儲存庫必須實作介面QueryByExampleExecutor 。在我們的例子中,該介面已經在JpaRepository中擴展,我們通常在儲存庫中擴展該介面。因此,沒有必要明確地實現它。
現在,讓我們建立一個Student範例以包含我們要過濾的三個選擇標準:
School schoolExample = new School();
schoolExample.setBorough("Ealing");
Student studentExample = new Student();
studentExample.setAge(20);
studentExample.setName("Smith");
studentExample.setSchool(schoolExample);
Example example = Example.of(studentExample);
設定範例後,我們呼叫儲存庫findAll(…)方法來取得結果:
List<Student> studentList = studentRepository.findAll(example);
但是,上面的範例僅支援精確匹配。如果我們想取得名字以「 Smith 」結尾的學生,我們需要客製化配對策略。 Query by example 提供了ExampleMatcher類別來執行此操作。我們需要做的就是在name欄位上建立一個ExampleMatcher並將其套用到Example實例:
ExampleMatcher customExampleMatcher = ExampleMatcher.matching()
.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.endsWith().ignoreCase());
Example<Student> example = Example.of(studentExample, customExampleMatcher);
我們在這裡使用的ExampleMatcher是不言自明的。它使名稱欄位在匹配時不區分大小寫,並確保值以我們範例中指定的名稱結尾。
透過Example查詢易於理解和實現。但是,它不支援更複雜的查詢,例如大於或小於欄位的條件。
4.依Specification查詢
Spring Data JPA 中的依Specification查詢允許使用Specification介面基於一組條件建立動態查詢。
與傳統方法(例如衍生查詢方法或使用@Query的自訂查詢)相比,這種方法更加靈活。它對於複雜的查詢要求或需要在運行時動態調整查詢時非常有用。
與範例查詢類似,我們的儲存庫方法必須擴展一個介面才能啟用此功能。這次,我們需要擴充JpaSpecificationExecutor :
public interface StudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {
}
接下來,我們必須為每個篩選條件定義三個方法。 Specification並不限制我們對一種過濾條件使用一種方法。這主要是為了清晰起見並使其更具可讀性:
public class StudentSpecification {
public static Specification<Student> nameEndsWithIgnoreCase(String name) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), "%" + name.toLowerCase());
}
public static Specification<Student> isAge(int age) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("age"), age);
}
public static Specification<Student> isSchoolBorough(String borough) {
return (root, query, criteriaBuilder) -> {
Join<Student, School> scchoolJoin = root.join("school");
return criteriaBuilder.equal(scchoolJoin.get("borough"), borough);
};
}
}
從上面的方法我們知道,我們使用CriteriaBuilder來建構濾波條件。 CriteriaBuilder幫助我們以程式設計方式在 JPA 中建立動態查詢,並為我們提供類似於編寫 SQL 查詢的靈活性。它允許我們使用equal(…)和like(…)等方法創建謂詞來定義條件。
在更複雜的操作的情況下,例如將附加表與基底表連接時,我們將使用Root.join(…) 。根充當 FROM 子句的錨點,提供對實體屬性和關係的存取。
現在,讓我們呼叫儲存庫方法來取得Specification過濾的結果:
Specification<Student> studentSpec = Specification
.where(StudentSpecification.nameEndsWithIgnoreCase("smith"))
.and(StudentSpecification.isAge(20))
.and(StudentSpecification.isSchoolBorough("Ealing"));
List<Student> studentList = studentRepository.findAll(studentSpec);
5.透過QueryDsl查詢
與Example相比, Specification強大,能夠處理更複雜的查詢。然而,當我們處理包含許多選擇條件的複雜查詢時, Specification介面可能會變得冗長且難以閱讀。
這就是 QueryDSL 試圖用更直觀的解決方案來解決Specification的局限性的地方。它是一個類型安全的框架,用於以直覺、可讀和強類型的方式建立動態查詢。
為了使用 QueryDSL,我們需要添加一些依賴項。讓我們將以下Querydsl JPA和APT 支援依賴項新增到pom.xml中:
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.1.0</version>
<classifier>jakarta</classifier>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.1.0</version>
<classifier>jakarta</classifier>
<scope>provided</scope>
</dependency>
值得注意的是,JPA 3.0 中 JPA 的套件名稱從javax.persistence改為jakarata.persistence 。如果我們使用 3.0 及更高版本,我們必須將jakarta分類器放入依賴項中。
除了依賴項之外,我們還必須在pom.xml的插件部分中包含以下註釋處理器:
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
該處理器在編譯時為我們的實體類別產生元模型類別。將這些設定合併到我們的應用程式中並進行編譯後,我們將看到 Querydsl 在建置資料夾中產生兩種查詢類型QStudent和QSchool 。
以類似的方式,我們這次必須包含QuerydslPredicateExecutor ,以使我們的儲存庫能夠透過這些查詢類型取得結果:
public interface StudentRepository extends JpaRepository<Student, Long>, QuerydslPredicateExecutor<Student>{
}
接下來,我們將根據這些查詢類型建立一個動態查詢,並在我們的StudentRepository中使用它進行查詢。這些查詢類型已經包含了對應實體類別的所有屬性。因此,我們可以直接引用建構謂詞時所需的欄位:
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);
如上面的程式碼所示,使用查詢類型定義查詢條件非常簡單直覺。
儘管設定所需的依賴關係很複雜,這是一項一次性任務,但它提供了與Specification相同的流暢方式,直觀且易於閱讀。
而且,我們不需要按照Specification類別中的要求手動明確定義過濾條件。
六、結論
在本文中,我們探索了在 Spring Data JPA 中建立動態查詢的不同方法。
-
Example查詢最適合簡單、精確比對的查詢。 - 如果我們需要更多類似 SQL 的表達式和比較,則按
Specification查詢非常適合中等複雜的查詢。 - QueryDSL 查詢最適合高度複雜的查詢,因為它使用查詢類型定義條件很簡單。
與往常一樣,我們範例的完整原始程式碼可以在 GitHub 上找到。