自定義 Repository 實現
Spring Data 提供了多種建立查詢方法的方式,只需少量編碼即可。但是,當這些選項不能滿足您的需求時,您也可以為 repository 方法提供自己的自定義實現。本節將介紹如何做到這一點。
定製單個 Repository
要透過自定義功能豐富一個 repository,您必須首先定義一個片段介面(fragment interface)和該自定義功能的實現,如下所示
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
@Override
public void someCustomMethod(User user) {
// Your custom implementation
}
}
與片段介面對應的類名中最重要的部分是 |
從歷史上看,Spring Data 自定義 repository 實現的發現遵循一個命名模式,該模式從 repository 中派生自定義實現類名,從而有效地只允許一個自定義實現。 位於與 repository 介面相同包中、名稱匹配 repository 介面名稱 加上 實現字尾 的型別被視為自定義實現,並將被當作自定義實現處理。遵循該名稱的類可能導致不期望的行為。 我們將單自定義實現命名視為已廢棄,並建議不要使用此模式。請改為遷移到基於片段的程式設計模型。 |
實現本身不依賴於 Spring Data,可以是一個普通的 Spring Bean。因此,您可以使用標準的依賴注入行為來注入對其他 Bean(如 JdbcTemplate
)的引用,參與 AOP 切面等。
然後,您可以讓您的 repository 介面擴充套件該片段介面,如下所示
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
透過讓您的 repository 介面擴充套件片段介面,可以將 CRUD 功能與自定義功能結合起來,並使其對客戶端可用。
Spring Data repositories 是透過使用片段(fragments)來實現的,這些片段構成了 repository 組合。片段包括基礎 repository、功能切面(例如 Querydsl)以及自定義介面及其實現。每次向您的 repository 介面新增一個介面時,您都是透過新增一個片段來增強組合。基礎 repository 和 repository 切面實現由每個 Spring Data 模組提供。
以下示例展示了自定義介面及其實現
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
@Override
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
@Override
public void someContactMethod(User user) {
// Your custom implementation
}
@Override
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
以下示例展示了一個擴充套件 CrudRepository
的自定義 repository 的介面
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
Repositories 可以由多個自定義實現組成,這些實現按照宣告的順序匯入。自定義實現的優先順序高於基礎實現和 repository 切面。這種排序允許您覆蓋基礎 repository 和切面方法,並在兩個片段提供相同方法簽名時解決歧義。Repository 片段不限於在單個 repository 介面中使用。多個 repositories 可以使用同一個片段介面,從而允許您在不同的 repositories 之間重用自定義功能。
以下示例展示了一個 repository 片段及其實現
save(…)
的片段interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
@Override
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
以下示例展示了一個使用上述 repository 片段的 repository
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
repository 基礎設施會嘗試透過掃描在其找到 repository 的包下的類來自動檢測自定義實現片段。這些類需要遵循命名約定,即附加一個預設字尾 Impl
。
以下示例展示了一個使用預設字尾的 repository 和一個設定自定義字尾值的 repository
-
Java
-
XML
@EnableMongoRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
前一個示例中的第一個配置嘗試查詢一個名為 com.acme.repository.CustomizedUserRepositoryImpl
的類作為自定義 repository 實現。第二個示例嘗試查詢 com.acme.repository.CustomizedUserRepositoryMyPostfix
。
歧義的解決
如果在不同的包中找到多個類名匹配的實現,Spring Data 會使用 bean 名稱來確定使用哪一個。
考慮到前面展示的 CustomizedUserRepository
的以下兩個自定義實現,將使用第一個實現。它的 bean 名稱是 customizedUserRepositoryImpl
,與片段介面 (CustomizedUserRepository
) 的名稱加上字尾 Impl
相匹配。
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
如果您使用 @Component("specialCustom")
註解 UserRepository
介面,那麼 bean 名稱加上 Impl
將匹配在 com.acme.impl.two
中定義的 repository 實現的名稱,並且將使用它而不是第一個。
手動裝配
如果您的自定義實現僅使用基於註解的配置和自動裝配,則前面展示的方法執行良好,因為它被視為任何其他 Spring bean。如果您的實現片段 bean 需要特殊裝配,您可以宣告該 bean 並根據上一節中描述的約定為其命名。然後,基礎設施會按名稱引用手動定義的 bean 定義,而不是自己建立一個。以下示例展示瞭如何手動裝配自定義實現
-
Java
-
XML
class MyClass {
MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {
…
}
}
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
使用 spring.factories 註冊片段
如配置一節中已經提到的,基礎設施只自動檢測 repository 基礎包內的片段。因此,如果片段位於其他位置或希望由外部歸檔檔案貢獻,並且它們不共享公共名稱空間,則將找不到它們。在 spring.factories
中註冊片段可以繞過此限制,如下一節所述。
假設您希望為您的組織提供一些自定義搜尋功能,該功能可跨多個 repositories 使用,並利用文字搜尋索引。
首先,您只需要片段介面。注意泛型引數 <T>
,用於使片段與 repository 域型別對齊。
package com.acme.search;
public interface SearchExtension<T> {
List<T> search(String text, Limit limit);
}
假設實際的全文搜尋透過一個註冊為上下文中的 Bean
的 SearchService
可用,這樣您就可以在我們的 SearchExtension
實現中呼叫它。執行搜尋所需的一切只是集合(或索引)名稱和一個物件對映器,它將搜尋結果轉換為實際的域物件,如下所示。
package com.acme.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Limit;
import org.springframework.data.repository.core.RepositoryMethodContext;
class DefaultSearchExtension<T> implements SearchExtension<T> {
private final SearchService service;
DefaultSearchExtension(SearchService service) {
this.service = service;
}
@Override
public List<T> search(String text, Limit limit) {
return search(RepositoryMethodContext.getContext(), text, limit);
}
List<T> search(RepositoryMethodContext metadata, String text, Limit limit) {
Class<T> domainType = metadata.getRepository().getDomainType();
String indexName = domainType.getSimpleName().toLowerCase();
List<String> jsonResult = service.search(indexName, text, 0, limit.max());
return jsonResult.stream().map(…).collect(toList());
}
}
在上面的示例中,RepositoryMethodContext.getContext()
用於檢索實際方法呼叫的元資料。RepositoryMethodContext
暴露附加到 repository 的資訊,例如域型別。在這種情況下,我們使用 repository 域型別來標識要搜尋的索引名稱。
暴露呼叫元資料是昂貴的,因此預設是停用的。要訪問 RepositoryMethodContext.getContext()
,您需要通知負責建立實際 repository 的 repository 工廠來暴露方法元資料。
-
標記介面
-
Bean 後置處理器
將 RepositoryMetadataAccess
標記介面新增到片段實現將觸發基礎設施併為那些使用該片段的 repositories 啟用元資料暴露。
package com.acme.search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Limit;
import org.springframework.data.repository.core.support.RepositoryMetadataAccess;
import org.springframework.data.repository.core.RepositoryMethodContext;
class DefaultSearchExtension<T> implements SearchExtension<T>, RepositoryMetadataAccess {
// ...
}
可以透過 BeanPostProcessor
直接在 repository 工廠 bean 上設定 exposeMetadata
標誌。
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.lang.Nullable;
@Configuration
class MyConfiguration {
@Bean
static BeanPostProcessor exposeMethodMetadata() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if(bean instanceof RepositoryFactoryBeanSupport<?,?,?> factoryBean) {
factoryBean.setExposeMetadata(true);
}
return bean;
}
};
}
}
請不要只複製/貼上以上內容,而要考慮您的實際用例,這可能需要更細粒度的方法,因為以上方法會簡單地在每個 repository 上啟用該標誌。
在片段宣告和實現都到位後,您可以在 META-INF/spring.factories
檔案中註冊擴充套件,並在需要時打包。
META-INF/spring.factories
中註冊片段com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
現在您就可以使用您的擴充套件了;只需將介面新增到您的 repository 中即可。
package io.my.movies;
import com.acme.search.SearchExtension;
import org.springframework.data.repository.CrudRepository;
interface MovieRepository extends CrudRepository<Movie, String>, SearchExtension<Movie> {
}
定製基礎 Repository
當您想要定製基礎 repository 行為以影響所有 repositories 時,上一節中描述的方法要求定製每個 repository 介面。為了改變所有 repositories 的行為,您可以建立一個擴充套件特定於持久化技術的 repository 基礎類的實現。然後,該類充當 repository 代理的自定義基礎類,如下例所示
class MyRepositoryImpl<T, ID>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
@Override
@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}
該類需要有一個超類建構函式,供特定於儲存的 repository 工廠實現使用。如果 repository 基礎類有多個建構函式,請覆蓋接受 EntityInformation 以及特定於儲存的基礎設施物件(如 EntityManager 或模板類)的那個。 |
最後一步是讓 Spring Data 基礎設施知曉定製的 repository 基礎類。在配置中,您可以使用 repositoryBaseClass
來實現,如下例所示
-
Java
-
XML
@Configuration
@EnableMongoRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />