自訂儲存庫實作
Spring Data 提供了多種選項,讓您可以用少量的程式碼建立查詢方法。但是,當這些選項不符合您的需求時,您也可以為儲存庫方法提供自己的自訂實作。本節將說明如何做到這一點。
自訂個別儲存庫
若要使用自訂功能豐富儲存庫,您必須先定義片段介面和自訂功能的實作,如下所示
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
@Override
public void someCustomMethod(User user) {
// Your custom implementation
}
}
對應於片段介面的類別名稱中最重要的部分是 |
從歷史上看,Spring Data 自訂儲存庫實作探索遵循命名模式,該模式從儲存庫派生自訂實作類別名稱,從而有效地允許單一自訂實作。 位於與儲存庫介面相同套件中的類型,符合「儲存庫介面名稱」後接「實作後綴」,則被視為自訂實作,並將被視為自訂實作。遵循該名稱的類別可能會導致不良行為。 我們認為單一自訂實作命名已被棄用,並建議不要使用此模式。請改為遷移到基於片段的程式設計模型。 |
實作本身不依賴 Spring Data,並且可以是常規 Spring Bean。因此,您可以使用標準的依賴注入行為來注入對其他 Bean(例如 JdbcTemplate
)的引用、參與面向切面編程等等。
然後,您可以讓您的儲存庫介面擴充片段介面,如下所示
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
使用您的儲存庫介面擴充片段介面結合了 CRUD 和自訂功能,並使其可供客戶端使用。
Spring Data 儲存庫是透過使用片段來實作的,這些片段形成儲存庫組合。片段是基礎儲存庫、功能方面(例如 QueryDsl)以及自訂介面及其實作。每次您將介面新增至儲存庫介面時,您都會透過新增片段來增強組合。基礎儲存庫和儲存庫方面實作由每個 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
的自訂儲存庫的介面
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
儲存庫可以由多個自訂實作組成,這些實作按照其宣告的順序匯入。自訂實作的優先順序高於基礎實作和儲存庫方面。此順序可讓您覆寫基礎儲存庫和方面方法,並在兩個片段貢獻相同的方法簽章時解決歧義。儲存庫片段不限於在單一儲存庫介面中使用。多個儲存庫可以使用片段介面,讓您跨不同儲存庫重複使用自訂項目。
以下範例顯示儲存庫片段及其實作
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
}
}
以下範例顯示使用前述儲存庫片段的儲存庫
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
組態
儲存庫基礎架構會嘗試透過掃描在其找到儲存庫的套件下方的類別來自動偵測自訂實作片段。這些類別需要遵循命名慣例,即附加預設為 Impl
的後綴。
以下範例顯示使用預設後綴的儲存庫以及為後綴設定自訂值的儲存庫
-
Java
-
XML
@EnableCassandraRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
前述範例中的第一個組態嘗試查找名為 com.acme.repository.CustomizedUserRepositoryImpl
的類別以充當自訂儲存庫實作。第二個範例嘗試查找 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
中為儲存庫實作定義的名稱相符,並且將使用它來代替第一個。
手動裝配
如果您的自訂實作僅使用基於註解的組態和自動裝配,則先前顯示的方法運作良好,因為它被視為任何其他 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 註冊片段
如「組態」章節中已經提到的,基礎架構僅自動偵測儲存庫基礎套件中的片段。因此,如果片段位於另一個位置或想要由外部封存檔貢獻,則如果它們不共用通用命名空間,則將找不到它們。在 spring.factories
中註冊片段可讓您規避此限制,如下一節所述。
想像一下,您想要為您的組織提供一些可在多個儲存庫中使用的自訂搜尋功能,並利用文字搜尋索引。
首先,您只需要片段介面。請注意泛型 <T>
參數,使片段與儲存庫網域類型對齊。
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
公開附加到儲存庫的資訊,例如網域類型。在本例中,我們使用儲存庫網域類型來識別要搜尋的索引名稱。
公開調用元資料的成本很高,因此預設情況下會停用它。若要存取 RepositoryMethodContext.getContext()
,您需要建議負責建立實際儲存庫的儲存庫工廠公開方法元資料。
-
標記介面
-
Bean 後處理器
將 RepositoryMetadataAccess
標記介面新增至片段實作將觸發基礎架構,並為使用該片段的儲存庫啟用元資料公開。
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 {
// ...
}
exposeMetadata
標誌可以直接在儲存庫工廠 Bean 上透過 BeanPostProcessor
設定。
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;
}
};
}
}
請不要只是複製/貼上上述內容,而是考慮您的實際用例,這可能需要更細緻的方法,因為上述內容只會在每個儲存庫上啟用該標誌。
具有片段宣告和實作後,您可以將擴充功能註冊在 META-INF/spring.factories
檔案中,並在需要時將內容封裝起來。
META-INF/spring.factories
中註冊片段com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
現在您已準備好使用您的擴充功能;只需將介面新增至您的儲存庫即可。
package io.my.movies;
import com.acme.search.SearchExtension;
import org.springframework.data.repository.CrudRepository;
interface MovieRepository extends CrudRepository<Movie, String>, SearchExtension<Movie> {
}
自訂基礎儲存庫
當您想要自訂基礎儲存庫行為以使所有儲存庫都受到影響時,前述章節中描述的方法需要自訂每個儲存庫介面。為了更改所有儲存庫的行為,您可以建立一個擴充特定於持久性技術的儲存庫基礎類別的實作。然後,此類別充當儲存庫代理的自訂基礎類別,如下列範例所示
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
}
}
該類別需要具有超類別的建構子,商店特定的儲存庫工廠實作會使用它。如果儲存庫基礎類別有多個建構子,請覆寫採用 EntityInformation 以及商店特定基礎架構物件(例如 EntityManager 或範本類別)的建構子。 |
最後一步是讓 Spring Data 基礎架構知道自訂的儲存庫基礎類別。在組態中,您可以透過使用 repositoryBaseClass
來做到這一點,如下列範例所示
-
Java
-
XML
@Configuration
@EnableCassandraRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />