Spring Data AOT 儲存庫簡介
1. 概述
近年來,Java 生態系統投入了大量精力來提升 JVM 的效能。我們見證了 JIT 編譯器、預編譯 (AOT) 以及最近的原生鏡像的出現。
流行的 Java 框架,例如 Micronaut 和 Spring,都朝著同一個方向發展,透過編譯最佳化來提升效能。 Spring 同時支援 AOT(預編譯)和 Native Image(原生鏡像)。然而,在這些大型框架中,反射的使用仍然很普遍,因此仍有很大的改進空間。 Spring Boot 4 即將推出的最新特性是針對 Spring Data 的 AOT 最佳化,即 AOT 倉庫。
本文將介紹 Spring Data AOT Repositories 的新特性。由於該特性尚未在任何正式版中提供,我們將使用一個里程碑版本進行演示。隨後,我們將演示其工作原理。最後,我們將比較它在啟用和停用 AOT 編譯的 Spring Boot 3 環境中與現有替代方案的使用情況。
2. 依賴關係
首先,我們來定義示範 Spring Data AOT 倉庫所需的最小相依性。我們只需要保留spring-boot-starter-web和spring-boot-starter-data-jpa兩個依賴項。前者用於提供一個端點來驗證倉庫是否正常運作,後者則包含 Spring 與資料庫互動所需的一切:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>4.0.5</version>
</dependency>
Spring Boot 4.0 版本目前仍處於早期開發階段。我們的演示仍將使用穩定的4.0.5版本。
3. Spring 資料儲存庫
接下來,我們來看看儲存庫的實作是如何從執行時間代理程式演變為 Spring Data AOT 儲存庫的建置時產生程式碼的。我們將使用一個非常簡單且常用的範例:一個User實體及其對應的UserRepository 。這將使我們能夠比較三種儲存庫選項的行為和效能:不使用 AOT 的儲存庫、使用 AOT 的儲存庫(但在 AOT 儲存庫出現之前)以及 AOT 儲存庫。
3.1 領域模型
為了演示目的,我們將使用一個只有三個欄位的簡短User實體:
@Entity
@Table(name = "USERS")
public class User {
@Id
@GeneratedValue
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
// constructors, setters, getters, equals, etc.
}
實體類別僅包含id 、 firstName和lastName欄位。我們可以使用所有我們熟悉的 Spring Data 和 JPA 註解。然後我們將建立相關的UserRepository :
public interface UserRepository extends Repository<User, Long> {
User save(User user);
@Transactional(readOnly = true)
List<User> findAll();
List<User> findAllById(Iterable<Long> longs);
@Query(value = "SELECT * FROM users", nativeQuery = true)
List<User> nativeQueryFindAllUsers();
@Query(value = "SELECT u FROM User u")
List<User> queryFindAllUsers();
}
首先,我們將像往常一樣新增三個 Spring Data 可以轉換為查詢的方法。除此之外,我們還會加入兩個使用@Query註解的方法,一個是原生方法,一個是非原生方法。這樣做是為了了解 Spring Data AOT Repository 如何處理不同的情況。
其次,我們擴展了Repository接口,使其更加通用。先前引入的依賴項會將其轉換為JpaRepository 。
3.2. 不使用 AOT 的 Spring 資料儲存庫
在 Spring AOT 最佳化和 Spring Data AOT 儲存庫出現之前,我們會使用mvn clean install指令編譯程式碼。這會將 Java 文件轉換為類,除了縮排之外,沒有任何其他更改。
這意味著實作並非自動生成。我們知道,一切都在運行時發生:
- Spring 會找到
Repository介面。 - 將它們傳遞給
JpaRepositoryFactory -
JpaRepositoryFactory建構SimpleJpaRepository實例 - 然後,它會將每個實例包裝在動態代理中,該代理會將來自介面方法(例如
findAll()呼叫路由到SimpleJpaRepository上的對應方法、Query和JPQL實現,或任何自訂實作。
所有程式碼都是基於反射在運行時建構的。這意味著啟動時間和記憶體佔用都會受到影響。
3.3. Spring Data AOT 倉庫之前的 AOT 倉庫
Spring 6 引進了預先最佳化 (AOT) 功能。要啟用此功能,我們需要:
- 編譯程式碼(包括
spring-boot:process-aot任務),或設定建置插件以預設啟用 AOT 編譯。 - 執行應用程序,並將
spring.aot.enabled屬性設為true
關於 Spring Data Repositories,結果與之前相同。這意味著我們得到的是同一個類,然後 Spring 在運行時使用SimpleJpaRepository 、反射和代理來實現所有功能。差別在於 AOT 編譯會建立一個額外的類別UserRepository__BeanDefinitions ,其中包含一些反射元資料和其他在建置時預先計算的資訊。這可以縮短啟動時間。
3.4. Spring Data AOT 儲存庫
Spring Data 4 的一項新特性是 Spring Data AOT Repositories。借助此特性,Spring 將應用程式啟動時執行的所有 Repositories 準備工作轉移到了建置時。
讓我們從宏觀層面來了解它的工作原理。 Spring利用儲存庫的儲存特性,將你的儲存庫查詢方法轉換為實際的原始碼。已實現的方法可以直接使用。例如,在我們的範例中, save()方法已在SimpleJpaRepository中實作。 Spring 不需要為這些方法產生任何程式碼。但是,其餘方法將在UserRepositoryImpl__AotRepository類別中實作。
使用 AOT 編譯程式碼的方式與先前的 Spring 版本相同。我們可以在target/classes資料夾中找到產生的類別:
@Generated
public class UserRepositoryImpl__AotRepository extends AotRepositoryFragmentSupport {
private final RepositoryFactoryBeanSupport.FragmentCreationContext context;
private final EntityManager entityManager;
public List<User> nativeQueryFindAllUsers() {
String var1 = "SELECT * FROM users";
Query var2 = this.entityManager.createNativeQuery(var1, User.class);
return var2.getResultList();
}
public List<User> queryFindAllUsers() {
String var1 = "SELECT u FROM User u";
Query var2 = this.entityManager.createQuery(var1);
return var2.getResultList();
}
}
使用 `@Query` 註解UserRepository__BeanDefinitions @Query包含與未使用 Spring Data 時相同的程式碼。 `UserRepository__BeanDefinitions` 類別仍然存在,與先前的版本類似。此新功能產生的最後一個來源檔案是包含原生鏡像提示的 JSON 檔案 ` UserRepository.json 。
這樣,Spring 就能在建置時產生具體的倉庫實現,從而消除代理程式和大部分運行時反射。這既能縮短啟動時間,又能降低記憶體佔用。
要啟用 Spring Data AOT 儲存庫,我們需要在執行應用程式時將spring.aot.repositories.enabled屬性設為true:
mvn spring-boot:run -Dspring.aot.enabled=true -Dspring.aot.repositories.enabled=true
4. 對績效的影響
Spring Data AOT 儲存庫有望提升應用程式效能。正如Spring 部落格文章所述,啟動時間應該會縮短,記憶體消耗也會降低:
接下來,我們將比較目前討論的三種實作方式的效能:
- 在 AOT 之前使用 Spring,可以使用
java -jar spring-data-jpa-not-aot/target/spring-data-jpa-not-aot-0.0.1-SNAPSHOT.jar指令 - 使用
java -Dspring.aot.enabled=true -jar spring-data-jpa-aot/target/spring-data-jpa-aot-0.0.1-SNAPSHOT.jar指令啟用帶有 AOT 改良的 Spring。 - 使用
java -Dspring.aot.enabled=true -Dspring.aot.repositories.enabled=true -jar spring-data-jpa-aot-repository/target/spring-data-jpa-aot-repository-0.0.1-SNAPSHOT.jar指令建立 Spring Data AOT 儲存庫
我們將使用五個實體類別和儲存庫,以使變更更加直觀。同時,我們將重點放在啟動時間和記憶體消耗。
4.1 建置時間比較
如前所述,新的 AOT 儲存庫功能會在建置時執行額外的工作來建置儲存庫。因此,啟用此功能後,建置速度預計會變慢:
- 未啟用 AOT 的儲存庫:
Total time: 11.076 s - 使用 AOT 的儲存庫:
Total time: 17.166 s - AOT儲存庫:
Total time: 25.390 s
使用 Spring Data AOT 儲存庫時,如果 JPA 方法中存在語法錯誤,我們會看到編譯時錯誤。這是合理的,因為 JPA 方法到 SQL 語句的轉換現在是在編譯時而不是執行時進行的。
4.2 啟動時間比較
接下來,我們將執行一個腳本來啟動每個服務,同時嘗試存取一個使用程式碼倉庫的端點。這樣,我們就可以監控服務啟動所需的時間,因為伺服器和程式碼倉庫都已準備就緒。
對於沒有 AOT 的儲存庫,腳本輸出如下:
==== RESULTS ====
time elapsed 10148 millis
Process Specific Memory/CPU (RSS KB / CPU Time): 289840 00:00:28
對於具有 AOT 的儲存庫:
==== RESULTS ====
time elapsed 9885 millis
Process Specific Memory/CPU (RSS KB / CPU Time): 291104 00:00:25
對於 Spring Data AOT 儲存庫:
==== RESULTS ====
time elapsed 8745 millis
Process Specific Memory/CPU (RSS KB / CPU Time): 292864 00:00:23
這完全合理,因為我們知道 AOT 和 AOT 儲存庫旨在縮短啟動時間。即使對於像我們這樣的小型專案來說,這一點也顯而易見。
Spring 所宣傳的記憶體優化效果在這裡沒有體現,但對於我們的小型專案來說,這是很有意義的。
注意,如果不使用 AOT 啟動應用程序,我們應該會看到一條日誌,顯示「 Starting Application 」。但使用 AOT 時,日誌應該會顯示「 Starting AOT-processed Application 。
4.3 負載下的比較
我們最後一項測試包含一個腳本,該腳本會向每個實作發送一些流量並比較效能。
對於沒有 AOT 的儲存庫,腳本輸出如下:
==== RESULTS ====
Total requests: 6688
Success (2xx): 6688
...
Avg duration (measured): 51.7629ms
...
Max memory utilised: 331888
對於具有 AOT 的儲存庫:
==== RESULTS ====
Total requests: 7664
Success (2xx): 7664
...
Avg duration (measured): 43.934ms
...
Max memory utilised: 334000
對於 Spring Data AOT 儲存庫:
==== RESULTS ====
Total requests: 6673
Success (2xx): 6673
...
Avg duration (measured): 49.0766ms
...
Max memory utilised: 337948
結果並未顯示 Spring 所宣傳的記憶體改進,但這可能會受到專案規模和資料儲存的影響。
5. 結論
本文探討了 Spring Boot 4 的新特性:Spring Data AOT Repositories。我們使用一個簡單的UserRepository類別來示範 Spring Data Repositories 之前的工作方式,以及新特性具體帶來了哪些變化。最後,我們比較了使用 Spring Data AOT Repositories 對效能的影響。
與往常一樣,範例的原始程式碼可以在 GitHub 上找到。