Spring Data 提前編譯的倉庫

工程 | Christoph Strobl | 2025年5月22日 | ...

在過去幾年中,我們看到整個 Java 生態系統大量投入,以減少應用程式啟動時間。主要關注點圍繞著預先編譯 (Ahead-of-Time, AOT) 最佳化。無論是將程式碼壓縮成 GraalVM 本機可執行檔案,還是使用協調檢查點恢復 (Coordinated Restore at Checkpoint, CRaC) 捕獲已最佳化的位元組碼,亦或是類資料共享 (Class Data Sharing, CDS) 或其最新繼任者 AOT 快取(Leyden 專案的一部分)。儘管不同方法的入門門檻各不相同,但它們都將效能最佳化從執行時轉移到更早的階段,例如構建時或單獨的打包步驟。

Spring 產品組合已為您準備就緒:無論您選擇哪個方向,它都能為您提供支援

  • 為 GraalVM 本機映象提供執行時提示
  • 為 Bean 定義和裝配生成 AOT 程式碼
  • 使用 Leyden 專案預主訓練執行進行應用程式上下文快照

隨著 Spring Data 4.0(如果您喜歡按日曆版本命名,則是 2025.1 釋出列車),我們將您的倉庫帶入了 AOT。我們將所有在應用程式啟動時進行的倉庫準備工作轉移到構建時。

它是如何工作的,您可以期待什麼?

簡而言之,當設定 spring.aot.repositories.enabled=true 配置屬性時,我們的 AOT 處理會透過依賴倉庫的特定儲存特性,將您的倉庫查詢方法轉換為實際的原始碼。生成的查詢方法包含您不使用 Spring Data 執行查詢時將編寫的完全相同的程式碼。然後,生成的原始碼會與您的應用程式一起編譯,並支援倉庫介面。

想象一個如下所述的寵物主人倉庫。

倉庫本身不繼承 CrudRepository 等基礎倉庫的任何功能,以將暴露的功能保持在最低限度。儘管如此,save 方法與預定義方法之一的簽名匹配,而列出的兩個查詢方法則使用派生和顯式註解方法。

interface OwnerRepository extends Repository<Owner, Integer> {

    Owner save(Owner owner);

    List<OwnerSummary> findAllByLastName(String lastName);

    @Transactional(readOnly = true)
    @Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%")
    Page<Owner> findByLastName(@Param("lastName") String lastName, Pageable pageable);

    // ...
}

在 AOT 階段,基礎設施將只考慮與程式碼生成相關的部分。以前面提到的 save 方法為例:由於我們在這裡使用 JPA,SimpleJpaRepository 已經為 save 方法提供了預設實現,允許程式碼生成跳過該方法。對於您的任何自定義實現也是如此。然而,OwnerRepository 的其餘兩個方法當然是 AOT 最佳化的物件,最終會出現在與源 OwnerRepository 相同包中的 OwnerRepositoryImpl__Aot 中。

@Generated
public class OwnerRepositoryImpl__Aot extends AotRepositoryFragmentSupport {

  private final EntityManager entityManager;

  public OwnerRepositoryImpl__Aot(EntityManager entityManager,
    RepositoryFactoryBeanSupport.FragmentCreationContext context) {
    // ...
  }

  /**
   * AOT generated implementation of {@link OwnerRepository#findAllByLastName(String)}.
   */
  public List<OwnerSummary> findAllByLastName(String lastName) {
    String queryString = "SELECT o.firstName AS firstName, o.lastName AS lastName, o.city AS city FROM org.springframework.samples.petclinic.owner.Owner o WHERE o.lastName = :lastName";
    Query query = this.entityManager.createQuery(queryString, Tuple.class);
    query.setParameter("lastName", lastName);

    return (List<OwnerSummary>) convertMany(query.getResultList(), false, OwnerSummary.class);
  }

  /**
   * AOT generated implementation of {@link OwnerRepository#findByLastName(String,Pageable)}.
   */
  public Page<Owner> findByLastName(String lastName, Pageable pageable) {
    String queryString = "SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName";
    String countQueryString = "SELECT count(DISTINCT owner) FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName";
    if (pageable.getSort().isSorted()) {
      DeclaredQuery declaredQuery = DeclaredQuery.jpqlQuery(queryString);
      queryString = rewriteQuery(declaredQuery, pageable.getSort(), Owner.class);
    }
    Query query = this.entityManager.createQuery(queryString);
    query.setParameter("lastName", "%s%%".formatted(lastName));
    if (pageable.isPaged()) {
      query.setFirstResult(Long.valueOf(pageable.getOffset()).intValue());
      query.setMaxResults(pageable.getPageSize());
    }
    LongSupplier countAll = () -> {
      Query countQuery = this.entityManager.createQuery(countQueryString);
      countQuery.setParameter("lastName", "%s%%".formatted(lastName));
      return (Long) countQuery.getSingleResult();
    };

    return PageableExecutionUtils.getPage((List<Owner>) query.getResultList(), pageable, countAll);
  }

}

如您所見,生成的程式碼可能非常簡單,也可能隨著查詢、引數繫結或請求的資料及其表示而變得複雜。在應用程式啟動時,AOT 生成的類會被連線到支援為倉庫介面建立的代理的倉庫組合中。因此,您第一次可以實際看到並進入在倉庫介面上呼叫方法時執行的程式碼。

除了可除錯性之外,預生成程式碼還有助於解析查詢和探索假設。它縮短了倉庫引導期間的程式碼路徑,從而加快了整體應用程式啟動速度並減少了記憶體佔用。

根據底層資料儲存的不同,這種減少可能相當顯著,例如對於 Spring Data JPA,它在已經更高效的 AOT 最佳化之上,還能帶來額外的啟動速度提升和更少的記憶體佔用。

預先編譯倉庫目前是一個預覽功能,其第一個版本適用於 JPA(僅透過 Hibernate)和 MongoDB,更多模組將在即將釋出的里程碑中跟進。

請試用這個新功能,並隨時與我們聯絡,告訴我們您的想法。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有