擴充套件 Spring Data Repositories 變得更容易

工程 | Christoph Strobl | 2024 年 12 月 03 日 | ...

自 Spring Data Repositories 誕生以來,它就被設計為可擴充套件的,無論您是想自定義單個查詢方法,還是提供一個全新的基礎實現。

2024.1 版本增強了您擴充套件 repository 的能力,透過 自定義功能,讓任何人都可以輕鬆建立可在不同專案中共享的擴充套件。

讓我們透過一個例子來了解它在實踐中是如何運作的。

假設您使用 MongoDB 作為文件儲存來管理電影資料庫。 您希望透過您的 repository 介面利用 MongoDB Atlas 的 向量搜尋 功能來實現 AI 驅動的搜尋操作。 通常,您會建立像這樣的自定義 repository 片段

package io.movie.db;

interface AtlasMovieRepository {
   List<Movie> vectorSearch(String index, String path, List<Double> vector, Limit limit);
}

在這裡,由於您正在使用 Movie 型別,因此您已經知道集合。 index 引數指定要使用的向量索引,path 定義儲存用於比較的 向量嵌入 的欄位。 相似度函式(例如,歐幾里得、餘弦或點積)在您設定索引時確定。 讓我們假設已經有一個餘弦向量索引。

在您的片段實現中,您需要建立 $vectorSearch 聚合階段,這是 MongoDB 執行向量搜尋的方法,並使用 MongoOperations 將其整合到 Aggregation API 中

package io.movie.db;

class AtlasMovieRepositoryFragment implements AtlasMovieRepository {

   private final MongoOperations mongoOperations;

   public AtlasMovieRepositoryFragment(MongoOperations mongoOperations) {
       this.mongoOperations = mongoOperations;
   }

   @Override
   public List<Movie> vectorSearch(String index, String path, List<Double> vector, Limit limit) {
       Document $vectorSearch = createSearchDocument(index, path, vector, limit);
       Aggregation aggregation = Aggregation.newAggregation(ctx -> $vectorSearch);
       return mongoOperations.aggregate(aggregation, "movies", Movie.class).getMappedResults();
   }

   private static Document createSearchDocument(String index, String path, List<Double> vector, Limit limit) {
       Document $vectorSearch = new Document();
       $vectorSearch.append("index", index);
       $vectorSearch.append("path", path);
       $vectorSearch.append("queryVector", vector);
       $vectorSearch.append("limit", limit.max());

       return new Document("$vectorSearch", $vectorSearch);
   }
}

現在,只需將片段整合到您的 MovieRepository

package io.movie.db;

interface MovieRepository extends CrudRepository<Movie, String>, AtlasMovieRepository { }

雖然這種方法有效,但您可能會注意到它與具有特定域型別 (Movie) 的單個 repository 緊密耦合。 這使得它難以在其他專案中重用,因為片段實現與 repository 的包相關,並且是特定於域的。

但是向量搜尋並不侷限於我們的電影資料庫。 如果我們想在其他專案中重用此功能,而無需複製和修改解決方案該怎麼辦? 讓我們探索一種使其更通用的方法。

使其可重用

為了實現重用,我們將 AtlasMovieRepository 及其實現移動到一個單獨的專案中,以便可以共享它。 然後,我們在 META-INF/spring.factories 檔案中註冊該片段,以便 Spring Data 瞭解該擴充套件

api.mongodb.atlas.AtlasMovieRepository=api.mongodb.atlas.AtlasMovieRepositoryFragment

但是,當前的實現仍然與 Movie 型別相關聯,從而限制了其可重用性。 為了解決這個問題,我們需要使片段更加通用。 將 AtlasMovieRepository 重新命名為 AtlasRepository 並引入泛型型別引數。 不要忘記更新 spring.factories 檔案。

package api.mongodb.atlas;

interface AtlasRepository<T> {
   List<T> vectorSearch(String index, String path, List<Double> vector, Limit limit);
}

接下來,我們更新實現以反映新的泛型方法,因為我們不能再假設我們正在定位 Movie 集合。 使用新引入的 RepositoryMethodContext,我們可以訪問 repository 元資料並動態確定適當的集合名稱

package api.mongodb.atlas;

class AtlasRepositoryFragment<T> implements AtlasRepository<T>, RepositoryMetadataAccess {

   private MongoOperations mongoOperations;

   public AtlasRepositoryFragment(MongoOperations mongoOperations) {
       this.mongoOperations = mongoOperations;
   }

   @Override
   public List<T> vectorSearch(String index, String path, List<Double> vector, Limit limit) {
       RepositoryMethodContext methodContext = RepositoryMethodContext.getContext();

       Class<?> domainType = methodContext.getMetadata().getDomainType();

       Document $vectorSearch = createSearchDocument(index, path, vector, limit);
       Aggregation aggregation = Aggregation.newAggregation(ctx -> $vectorSearch);
       return (List<T>) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType).getMappedResults();
   }

   private static Document createSearchDocument(String indexName, String path, List<Double> vector, Limit limit) {
       Document $vectorSearch = new Document();
       //…
   }
}

提供的方法上下文不僅允許您訪問有關 repository 的一般資訊,還允許您訪問 repository 的泛型、方法等。在上面的程式碼片段中,我們假設 repository 域型別與我們的自定義片段對齊,這可能不是這種情況。 因此,相反,我們還可以透過 ResolvableType.forClass(getRepositoryInterface()).as(AtlasRepository.class).getGeneric(0) 讀取介面的元件型別,甚至檢查當前方法的返回型別以應用其他操作,例如投影等。 為了簡單起見,讓我們堅持這個示例中的域型別。

為避免不必要的開銷,我們僅為需要它的 repository 啟用上下文訪問。 仔細檢視上面的程式碼,您會發現 AtlasRepositoryFragment 類上有一個額外的 RepositoryMetadataAccess 介面。 此標記介面建議基礎結構在方法呼叫時提供所需的元資料。

透過該設定,您現在可以透過簡單地擴充套件您的 repository 在任何專案中使用自定義擴充套件

package io.movie.db;

interface MovieRepository extends CrudRepository<Movie, String>, AtlasRepository<Movie> { }

要試用它,請訪問 Spring Data Examples 專案,您將在其中找到準備執行的程式碼。

獲取 Spring 新聞資訊

訂閱 Spring 新聞資訊,保持聯絡

訂閱

搶先一步

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

瞭解更多

獲得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

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

檢視所有