此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Redis 3.4.4! |
自定义存储库实施
Spring Data 提供了各种选项来创建查询方法,只需很少的编码。 但是,当这些选项不符合您的需求时,您也可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此作。
自定义单个存储库
要使用自定义功能丰富存储库,您必须首先定义自定义功能的片段接口和实现,如下所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
@Override
public void someCustomMethod(User user) {
// Your custom implementation
}
}
类名中与 fragment 接口对应的最重要的部分是 |
从历史上看, Spring Data 自定义存储库实现发现遵循一种命名模式,该模式从存储库派生自定义实现类名称,从而有效地允许单个自定义实现。 与仓库接口位于同一软件包中的类型,与仓库接口名称匹配,后跟 implementation 后缀。 被视为自定义实现,并将被视为自定义实现。 该名称后面的类可能会导致意外行为。 我们认为 single-custom implementation naming 已弃用,建议不要使用此模式。 相反,请迁移到基于 Fragment 的编程模型。 |
实现本身不依赖于 Spring Data,可以是常规的 Spring bean。
因此,您可以使用标准的依赖关系注入行为来注入对其他 bean(例如JdbcTemplate
)、参与方面,依此类推。
然后,您可以让您的存储库接口扩展 fragment 接口,如下所示:
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
@EnableRedisRepositories(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 名称来确定要使用的 Bean。
给定以下两个自定义实现CustomizedUserRepository
如前所述,使用了第一个实现。
它的 bean 名称是customizedUserRepositoryImpl
,它与 fragment 接口 (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
}
如果注释UserRepository
接口@Component("specialCustom")
、Bean 名称加Impl
然后匹配为com.acme.impl.two
,并使用它代替第一个。
手动布线
如果您的自定义实现仅使用基于 Comments 的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他 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
允许您规避此限制,如下一节所述。
假设您希望利用文本搜索索引为您的组织提供一些可在多个存储库中使用的自定义搜索功能。
首先,您只需要 fragment 接口。
请注意泛型<T>
参数将片段与存储库域类型对齐。
package com.acme.search;
public interface SearchExtension<T> {
List<T> search(String text, Limit limit);
}
假设实际的全文搜索可以通过SearchService
注册为Bean
这样你就可以在我们的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());
}
}
In the example above RepositoryMethodContext.getContext()
is used to retrieve metadata for the actual method invocation.
RepositoryMethodContext
exposes information attached to the repository such as the domain type.
In this case we use the repository domain type to identify the name of the index to be searched.
Exposing invocation metadata is costly, hence it is disabled by default.
To access RepositoryMethodContext.getContext()
you need to advise the repository factory responsible for creating the actual repository to expose method metadata.
Expose Repository Metadata
-
Marker Interface
-
Bean Post Processor
Adding the RepositoryMetadataAccess
marker interface to the fragments implementation will trigger the infrastructure and enable metadata exposure for those repositories using the fragment.
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 {
// ...
}
The exposeMetadata
flag can be set directly on the repository factory bean via a 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;
}
};
}
}
Please do not just copy/paste the above but consider your actual use case which may require a more fine-grained approach as the above will simply enable the flag on every repository.
Having both, the fragment declaration and implementation in place you can register the extension in the META-INF/spring.factories
file and package things up if needed.
Register the fragment in META-INF/spring.factories
com.acme.search.SearchExtension=com.acme.search.DefaultSearchExtension
Now you are ready to make use of your extension; Simply add the interface to your repository.
Using it
package io.my.movies;
import com.acme.search.SearchExtension;
import org.springframework.data.repository.CrudRepository;
interface MovieRepository extends CrudRepository<Movie, String>, SearchExtension<Movie> {
}
Customize the Base Repository
The approach described in the preceding section requires customization of each repository interfaces when you want to customize the base repository behavior so that all repositories are affected.
To instead change behavior for all repositories, you can create an implementation that extends the persistence technology-specific repository base class.
This class then acts as a custom base class for the repository proxies, as shown in the following example:
Custom repository base class
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
}
}
The class needs to have a constructor of the super class which the store-specific repository factory implementation uses.
If the repository base class has multiple constructors, override the one taking an EntityInformation
plus a store specific infrastructure object (such as an EntityManager
or a template class).
The final step is to make the Spring Data infrastructure aware of the customized repository base class.
In configuration, you can do so by using the repositoryBaseClass
, as shown in the following example:
Example 4. Configuring a custom repository base class
-
Java
-
XML
@Configuration
@EnableRedisRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />