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

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

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

2024.1 版本透過自定義功能增強了您擴充套件倉庫的能力,使任何人都可以比以往更輕鬆地建立可在不同專案之間共享的擴充套件。

讓我們透過一個示例來了解這在實踐中是如何工作的。

假設您正在使用 MongoDB 作為文件儲存來管理電影資料庫。您希望透過您的倉庫介面利用 MongoDB Atlas 向量搜尋 功能,以實現 AI 驅動的搜尋操作。通常,您會建立一個自定義倉庫片段,如下所示:

package io.movie.db;

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

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

在您的片段實現中,您需要建立 $vectorSearch 聚合階段,這是 MongoDB 執行向量搜尋的方法,並使用 MongoOperations 將其整合到聚合 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) 的單個倉庫緊密耦合。這使得在其他專案中難以重用,因為片段實現與倉庫的包繫結,並且是領域特定的。

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

使其可重用

為了實現重用,我們將 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,我們可以訪問倉庫元資料並動態確定適當的集合名稱。

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();
       //…
   }
}

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

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

透過此設定,您現在可以透過簡單地擴充套件您的倉庫來在任何專案中使用自定義擴充套件。

package io.movie.db;

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

要試用,請訪問 Spring Data 示例 專案,您將找到可執行的程式碼。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

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

瞭解更多

即將舉行的活動

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

檢視所有