Apache Solr 的 Spring Data (数据)

时间:2022-11-23 15:24:09

版本 4.3.15

Apache Solr 的 Spring Data (数据)

Spring Data for Apache Solr项目通过使用Apache Solr搜索引擎将Spring的核心概念应用于解决方案的开发。我们提供了一个“模板”作为存储和查询文档的高级抽象。您可能会注意到与Spring Framework中的MongoDB支持相似之处。

项目元数据

  • 版本控制:https://github.com/spring-projects/spring-data-solr
  • 布塔克:https://jira.spring.io/browse/DATASOLR
  • 发布库:https://repo.spring.io/libs-release
  • 里程碑存储库:https://repo.spring.io/libs-milestone
  • 快照存储库:https://repo.spring.io/libs-snapshot

要求

Spring Data Solr 需要 Java 8 运行时和Apache Solr8.0。我们建议使用最新的 8.0.x 版本。

以下 Maven 依赖项代码段显示了如何在项目中包含 Apache Solr:

<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>${solr.version}</version>
</dependency>

1. 新的和值得注意的

1.1. Apache Solr 2.1 春季数据中的新增功能

  • 通过使用自动选择Solr内核。SolrTemplate
  • 声明向上兼容 Apache Solr 6(包括 6.3)。
  • 支持组合和查询。FacetHighlight
  • 允许将单值多值字段读取到非集合属性中。
  • 使用本机 SolrJ 模式 API。

1.2. Apache Solr 2.0 春季数据中的新增功能

  • 升级到 Apache Solr 5。
  • 支持查询时。RequestMethod

1.3. Apache Solr 1.5 春季数据中的新增功能

  • 支持范围分面。
  • 自动前缀和后缀映射键(请参阅:在MappingSolrConverter 中)。@DynamicdynamicMappedFieldValues

1.4. Apache Solr 1.4 春季数据中的新增功能

  • 升级到最新的 Solr 4.10.x 发行版(需要 Java 7)。
  • 添加了对实时获取的支持。
  • 获取字段统计信息(最大值、最小值、总和、计数、平均值、缺失值、标准dev 和不同的计算)。
  • 用于在文档分数上自动添加投影(请参见:特殊字段)。@Score

2. 使用 Spring 数据存储库

Spring 数据存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。


Spring 数据存储库文档和您的模块



本章解释了 Spring 数据存储库的核心概念和接口。 本章中的信息来自 Spring 数据共享模块。 它使用 Java 持久性 API (JPA) 模块的配置和代码示例。 应将 XML 命名空间声明和要扩展的类型调整为所用特定模块的等效项。“命名空间参考​”涵盖了XML配置,所有支持存储库API的Spring Data模块都支持XML配置。“存储库查询关键字”涵盖了存储库抽象通常支持的查询方法关键字。 有关模块特定功能的详细信息,请参阅本文档有关该模块的章节。


2.1. 核心概念

Spring 数据存储库抽象中的中心接口是。 它采用要管理的域类以及域类的 ID 类型作为类型参数。 此接口主要充当标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。 CrudRepository接口为正在管理的实体类提供复杂的 CRUD 功能。​​Repository​

示例 1.界面​​CrudRepository​

public interface CrudRepository<T, ID> extends Repository<T, ID> {

<S extends T> S save(S entity);

Optional<T> findById(ID primaryKey);

Iterable<T> findAll();

long count();

void delete(T entity);

boolean existsById(ID primaryKey);

// … more functionality omitted.
}

保存给定的实体。

返回由给定 ID 标识的实体。

返回所有实体。

返回实体数。

删除给定实体。

指示具有给定 ID 的实体是否存在。

我们还提供特定于持久性技术的抽象,例如 asor。 这些接口扩展并公开了底层持久性技术的功能,以及相当通用的持久性技术无关的接口,例如。​​JpaRepository​​​​MongoRepository​​​​CrudRepository​​​​CrudRepository​

除此之外,还有一个PagingAndSortingRepository抽象,它添加了额外的方法来简化对实体的分页访问:​​CrudRepository​

示例 2.接口​​PagingAndSortingRepository​

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

Iterable<T> findAll(Sort sort);

Page<T> findAll(Pageable pageable);
}

要访问页面大小为 20 的第二页,您可以执行以下操作:​​User​

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));

除了查询方法之外,还可以对计数查询和删除查询进行查询派生。 以下列表显示了派生计数查询的接口定义:

例 3.派生计数查询

interface UserRepository extends CrudRepository<User, Long> {

long countByLastname(String lastname);
}

以下清单显示了派生删除查询的接口定义:

例 4.派生删除查询

interface UserRepository extends CrudRepository<User, Long> {

long deleteByLastname(String lastname);

List<User> removeByLastname(String lastname);
}

2.2. 查询方法

标准 CRUD 功能存储库通常对基础数据存储具有查询。 使用 Spring Data,声明这些查询变成了一个四步过程:

  1. 声明扩展存储库或其子接口之一的接口,并将其键入应处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … }
  1. 在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
  1. 设置 Spring 以使用JavaConfig或XML 配置为这些接口创建代理实例。
  1. 要使用 Java 配置,请创建类似于以下内容的类:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories
class Config { … }
  1. 要使用 XML 配置,请定义类似于以下内容的 Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<jpa:repositories base-package="com.acme.repositories"/>

</beans>

此示例中使用 JPA 命名空间。 如果将存储库抽象用于任何其他存储,则需要将其更改为存储模块的相应命名空间声明。 换句话说,您应该交换支持,例如,。jpamongodb

另外,请注意,JavaConfig 变体不会显式配置包,因为缺省情况下使用带注释的类的包。 要自定义要扫描的包,请使用特定于数据存储的存储库注释的属性之一。basePackage…@Enable${store}Repositories

  1. 注入存储库实例并使用它,如以下示例所示:
class SomeClient {

private final PersonRepository repository;

SomeClient(PersonRepository repository) {
this.repository = repository;
}

void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}

以下各节详细介绍了每个步骤:

  • 定义存储库接口
  • 定义查询方法
  • 创建存储库实例
  • Spring 数据存储库的自定义实现

2.3. 定义仓库接口

要定义存储库接口,首先需要定义特定于域类的存储库接口。 接口必须扩展并键入域类和 ID 类型。 如果要公开该域类型的 CRUD 方法,请扩展代替。​​Repository​​​​CrudRepository​​​​Repository​

2.3.1. 微调存储库定义

通常,存储库界面会扩展、或。 或者,如果您不想扩展 Spring 数据接口,您也可以使用来注释存储库接口。 扩展公开一组完整的方法来操作实体。 如果您希望对要公开的方法有选择性,请将要公开的方法复制到域存储库中。​​Repository​​​​CrudRepository​​​​PagingAndSortingRepository​​​​@RepositoryDefinition​​​​CrudRepository​​​​CrudRepository​

这样做可以让您在提供的 Spring 数据存储库功能之上定义自己的抽象。

下面的示例演示如何有选择地公开 CRUD 方法(在本例中为):​​findById​​​​save​

例 5.有选择地公开 CRUD 方法

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

Optional<T> findById(ID id);

<S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}

在前面的示例中,您为所有域存储库和公开以及定义了通用基本接口。这些方法被路由到 Spring Data 提供的您选择的存储的基本存储库实现中(例如,如果您使用 JPA,则实现是),因为它们与方法签名匹配。 因此,现在可以保存用户,按ID查找单个用户,并触发查询以按电子邮件地址查找。​​findById(…)​​​​save(…)​​​​SimpleJpaRepository​​​​CrudRepository​​​​UserRepository​​​​Users​

中间存储库接口带有注释。 确保将该注释添加到 Spring Data 在运行时不应为其创建实例的所有存储库接口。​​@NoRepositoryBean​

2.3.2. 使用具有多个 Spring 数据模块的存储库

在应用程序中使用唯一的 Spring 数据模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring 数据模块。 有时,应用程序需要使用多个 Spring 数据模块。 在这种情况下,存储库定义必须区分持久性技术。 当它在类路径上检测到多个存储库工厂时,Spring Data 进入严格的存储库配置模式。 严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring 数据模块绑定:

  1. 如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring 数据模块的有效候选者。
  2. 如果域类使用特定于模块的类型注释进行注释,则它是特定 Spring 数据模块的有效候选者。 Spring Data 模块接受第三方注释(例如 JPA)或提供自己的注释(例如 Spring Data MongoDB 和 Spring Data Elasticsearch)。@Entity@Document

以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:

例 6.使用特定于模块的接口的存储库定义

interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }

interface UserRepository extends MyBaseRepository<User, Long> { … }

​MyRepository​​并扩展其类型层次结构。 它们是 Spring Data JPA 模块的有效候选者。​​UserRepository​​​​JpaRepository​

以下示例显示了使用通用接口的存储库:

例 7.使用通用接口的存储库定义

interface AmbiguousRepository extends Repository<User, Long> { … }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }

​AmbiguousRepository​​并仅在其类型层次结构中扩展。 虽然这在使用唯一的 Spring 数据模块时很好,但多个模块无法区分这些存储库应该绑定到哪个特定的 Spring 数据。​​AmbiguousUserRepository​​​​Repository​​​​CrudRepository​

以下示例显示了一个使用带有注释的域类的存储库:

例 8.使用带有注释的域类的存储库定义

interface PersonRepository extends Repository<Person, Long> { … }

@Entity
class Person { … }

interface UserRepository extends Repository<User, Long> { … }

@Document
class User { … }

​PersonRepository​​引用,它用JPAannotation注释,所以这个存储库显然属于Spring Data JPA.references,它用Spring Data MongoDB的sannotation注释。​​Person​​​​@Entity​​​​UserRepository​​​​User​​​​@Document​

以下错误示例显示了一个使用具有混合注释的域类的存储库:

例 9.使用具有混合注释的域类的存储库定义

interface JpaPersonRepository extends Repository<Person, Long> { … }

interface MongoDBPersonRepository extends Repository<Person, Long> { … }

@Entity
@Document
class Person { … }

这个例子展示了一个同时使用JPA和Spring Data MongoDB注释的域类。 它定义了两个存储库,并且。 一个用于JPA,另一个用于MongoDB使用。 Spring 数据不再能够区分存储库,这会导致未定义的行为。​​JpaPersonRepository​​​​MongoDBPersonRepository​

存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定 Spring 数据模块的存储库候选者。 可以对同一域类型使用多个特定于持久性技术的注释,并允许跨多个持久性技术重用域类型。 但是,Spring Data 无法再确定绑定存储库的唯一模块。

区分存储库的最后一种方法是确定存储库基础包的范围。 基本包定义扫描存储库接口定义的起点,这意味着存储库定义位于相应的包中。 默认情况下,注释驱动的配置使用配置类的包。 基于 XML 的配置中的基本包是必需的。

以下示例显示了基本包的注释驱动配置:

例 10.注释驱动的基本包配置

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }

2.4. 定义查询方法

存储库代理有两种方法可以从方法名称派生特定于存储的查询:

  • 通过直接从方法名称派生查询。
  • 通过使用手动定义的查询。

可用选项取决于实际商店。 但是,必须有一个策略来决定创建什么实际查询。 下一节介绍可用选项。

2.4.1. 查询查找策略

存储库基础结构可以使用以下策略来解析查询。 使用 XML 配置,您可以通过属性在命名空间中配置策略。 对于 Java 配置,您可以使用注释的属性。 特定数据存储可能不支持某些策略。​​query-lookup-strategy​​​​queryLookupStrategy​​​​Enable${store}Repositories​

  • ​CREATE​​尝试从查询方法名称构造特定于存储的查询。 一般方法是从方法名称中删除一组给定的已知前缀,并分析方法的其余部分。 您可以在“查询创建”中阅读有关查询构造的更多信息。
  • ​USE_DECLARED_QUERY​​尝试查找已声明的查询,如果找不到查询,则会引发异常。 查询可以通过某处的注释定义,也可以通过其他方式声明。 请参阅特定商店的文档以查找该商店的可用选项。 如果存储库基础结构在引导时找不到该方法的声明查询,则会失败。
  • ​CREATE_IF_NOT_FOUND​​(默认值)组合沙。 它首先查找已声明的查询,如果未找到已声明的查询,则会创建一个基于名称的自定义方法查询。 这是默认的查找策略,因此,如果未显式配置任何内容,则使用此方法。 它允许按方法名称快速定义查询,但也允许根据需要引入声明的查询来自定义调整这些查询。CREATEUSE_DECLARED_QUERY

2.4.2. 查询创建

Spring 数据存储库基础结构中内置的查询生成器机制对于构建对存储库实体的约束查询非常有用。

下面的示例演示如何创建多个查询:

例 11.从方法名称创建查询

interface PersonRepository extends Repository<Person, Long> {

List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

分析查询方法名称分为主语和谓语。 第一部分 (,) 定义查询的主题,第二部分构成谓词。 引言子句(主语)可以包含进一步的表达式。 (或其他引入关键字)之间的任何文本都被视为描述性的,除非使用结果限制关键字之一,例如 ato 在要创建的查询上设置不同的标志或Top/First以限制查询结果。find…Byexists…ByfindByDistinct

附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。 但是,第一个充当分隔符来指示实际条件谓词的开始。 在非常基本的级别上,您可以定义实体属性的条件并将它们与 and 连接起来。​​By​​​​And​​​​Or​

分析方法的实际结果取决于为其创建查询的暂留存储。 但是,有一些一般事项需要注意:

  • 表达式通常是属性遍历与可以连接的运算符相结合。 可以将属性表达式与 and 组合在一起。 您还可以获得对运算符的支持,例如,,, 和属性表达式。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。ANDORBetweenLessThanGreaterThanLike
  • 方法解析器支持为单个属性(例如)或支持忽略大小写的类型的所有属性(通常是实例 — 例如,)设置 anflag。 是否支持忽略案例可能因商店而异,因此请参阅特定于商店的查询方法的参考文档中的相关部分。IgnoreCasefindByLastnameIgnoreCase(…)StringfindByLastnameAndFirstnameAllIgnoreCase(…)
  • 可以通过将 anclause 追加到引用属性的查询方法并提供排序方向 (or) 来应用静态排序。 要创建支持动态排序的查询方法,请参阅 “特殊参数处理”。OrderByAscDesc

2.4.3. 属性表达式

属性表达式只能引用托管实体的直接属性,如前面的示例所示。 在创建查询时,已确保分析的属性是托管域类的属性。 但是,也可以通过遍历嵌套属性来定义约束。 请考虑以下方法签名:

List<Person> findByAddressZipCode(ZipCode zipCode);

假设 ahas anwith a。 在这种情况下,该方法将创建属性遍历。 解析算法首先将整个部件 () 解释为属性,并检查域类中是否存在具有该名称(未大写)的属性。 如果算法成功,它将使用该属性。 如果没有,该算法将右侧驼峰案例部分的源拆分为头部和尾部,并尝试找到相应的属性 — 在我们的示例中,and。 如果算法找到具有该头部的属性,它将获取尾部并继续从那里向下构建树,以刚才描述的方式将尾部拆分。 如果第一个拆分不匹配,算法会将拆分点向左移动 (,) 并继续。​​Person​​​​Address​​​​ZipCode​​​​x.address.zipCode​​​​AddressZipCode​​​​AddressZip​​​​Code​​​​Address​​​​ZipCode​

尽管这应该适用于大多数情况,但算法可能会选择错误的属性。 假设该类也有属性。 算法将在第一轮拆分中匹配,选择错误的属性,然后失败(因为类型可能没有属性)。​​Person​​​​addressZip​​​​addressZip​​​​code​

要解决这种歧义,您可以在方法名称中使用手动定义遍历点。 所以我们的方法名称如下:​​_​

List<Person> findByAddress_ZipCode(ZipCode zipCode);

由于我们将下划线字符视为保留字符,因此强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰大小写)。

2.4.4. 特殊参数处理

若要处理查询中的参数,请定义方法参数,如前面的示例所示。 除此之外,基础架构还可以识别某些特定类型,例如and,以动态地将分页和排序应用于您的查询。 以下示例演示了这些功能:​​Pageable​​​​Sort​

例 12.使用 、 和 in 查询方法​​Pageable​​​​Slice​​​​Sort​

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

API 获取并期望将非值传递给方法。 如果您不想应用任何排序或分页,请使用和。​​Sort​​​​Pageable​​​​null​​​​Sort.unsorted()​​​​Pageable.unpaged()​

第一种方法允许您将实例传递给查询方法,以动态地将分页添加到静态定义的查询中。 了解可用元素和页面的总数。 它通过基础结构触发计数查询来计算总数。 由于这可能很昂贵(取决于所使用的商店),因此您可以改为返回 a。 仅知道 next是否可用,这在遍历较大的结果集时可能就足够了。​​org.springframework.data.domain.Pageable​​​​Page​​​​Slice​​​​Slice​​​​Slice​

排序选项也通过实例处理。 如果只需要排序,请向方法添加参数。 如您所见,返回 ais 也是可能的。 在这种情况下,不会创建构建实际实例所需的其他元数据(这反过来意味着不会发出所需的其他计数查询)。 相反,它将查询限制为仅查找给定范围的实体。​​Pageable​​​​org.springframework.data.domain.Sort​​​​List​​​​Page​

要了解整个查询获得的页面数,您必须触发额外的计数查询。 默认情况下,此查询派生自实际触发的查询。

分页和排序

可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。

例 13.定义排序表达式

Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());

有关定义排序表达式的更类型安全的方法,请从定义排序表达式的类型开始,并使用方法引用定义要排序的属性。

例 14。使用类型安全的 API 定义排序表达式

TypedSort<Person> person = Sort.sort(Person.class);

Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());

​TypedSort.by(…)​​通过(通常)使用 CGlib 来使用运行时代理,这在使用 Graal VM 本机等工具时可能会干扰本机映像编译。

如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:

例 15。使用 Querydsl API 定义排序表达式

QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));

2.4.5. 限制查询结果

可以使用 theor关键字来限制查询方法的结果,这些关键字可以互换使用。 您可以附加一个可选的数值来指定要返回的最大结果大小。 如果省略该数字,则假定结果大小为 1。 以下示例演示如何限制查询大小:​​first​​​​top​​​​top​​​​first​

例 16。限制查询的结果大小​​Top​​​​First​

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表达式还支持支持不同查询的数据存储的关键字。 此外,对于将结果集限制为一个实例的查询,支持使用 thekeyword 将结果包装到其中。​​Distinct​​​​Optional​

如果分页或切片应用于限制查询分页(以及可用页数的计算),则会在有限的结果中应用分页或切片。

通过使用参数限制结果和动态排序,可以表示“K”最小元素和“K”最大元素的查询方法。​​Sort​

2.4.6. 返回集合或可迭代对象的存储库方法

返回多个结果的查询方法可以使用标准 Java、and。 除此之外,我们还支持返回Spring Data,自定义扩展以及​​Vavr​​提供的集合类型。 请参阅解释所有可能的​​查询方法返回类型的​​附录。​​Iterable​​​​List​​​​Set​​​​Streamable​​​​Iterable​

使用可流式处理作为查询方法返回类型

您可以将其用作任何集合类型的替代方法。 它提供了访问非并行(缺少)的便捷方法,以及直接覆盖元素并将元素连接到其他元素的能力:​​Streamable​​​​Iterable​​​​Stream​​​​Iterable​​​​….filter(…)​​​​….map(…)​​​​Streamable​

例 17.使用可流式处理合并查询方法结果

interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}

Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
返回自定义可流式传输包装器类型

为集合提供专用包装器类型是一种常用模式,用于为返回多个元素的查询结果提供 API。 通常,通过调用返回类似集合类型的存储库方法并手动创建包装器类型的实例来使用这些类型。 您可以避免该额外步骤,因为如果满足以下条件,Spring Data 允许您将这些包装器类型用作查询方法返回类型:

  1. 类型实现。Streamable
  2. 该类型公开构造函数或名为 dorthat 的静态工厂方法作为参数。of(…)valueOf(…)Streamable

下面的清单显示了一个示例:

class Product {                                         
MonetaryAmount getPrice() { … }
}

@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> {

private final Streamable<Product> streamable;

public MonetaryAmount getTotal() {
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}


@Override
public Iterator<Product> iterator() {
return streamable.iterator();
}
}

interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text);
}

公开 API 以访问产品价格的实体。​​Product​

可以使用(使用龙目岛注释创建的工厂方法)构造的包装器类型。

一个标准的构造函数也这样做。​​Streamable<Product>​​​​Products.of(…)​​​​Streamable<Product>​

包装器类型公开一个额外的 API,计算新值。​​Streamable<Product>​

实现接口并委托给实际结果。​​Streamable​

该包装器类型可以直接用作查询方法返回类型。

您无需在存储库客户端中查询后返回并手动包装它。​​Products​​​​Streamable<Product>​

支持 Vavr 集合

Vavr是一个包含Java函数式编程概念的库。 它附带一组可用作查询方法返回类型的自定义集合类型,如下表所示:

Vavr 采集类型

使用的 Vavr 实现类型

有效的 Java 源类型

​io.vavr.collection.Seq​

​io.vavr.collection.List​

​java.util.Iterable​

​io.vavr.collection.Set​

​io.vavr.collection.LinkedHashSet​

​java.util.Iterable​

​io.vavr.collection.Map​

​io.vavr.collection.LinkedHashMap​

​java.util.Map​

您可以使用第一列中的类型(或其子类型)作为查询方法返回类型,并获取第二列中的类型用作实现类型,具体取决于实际查询结果(第三列)的 Java 类型。 或者,您可以声明(Vavr等效),然后我们从实际返回值中派生实现类。 也就是说,ais 变成了 Vavror,变成了 Vavr,依此类推。​​Traversable​​​​Iterable​​​​java.util.List​​​​List​​​​Seq​​​​java.util.Set​​​​LinkedHashSet​​​​Set​

2.4.7. 仓库方法的空处理

从Spring Data 2.0开始,返回单个聚合实例的存储库CRUD方法使用Java 8来指示可能缺少值。 除此之外,Spring Data 还支持在查询方法上返回以下包装器类型:​​Optional​

  • ​com.google.common.base.Optional​
  • ​scala.Option​
  • ​io.vavr.control.Option​

或者,查询方法可以选择根本不使用包装器类型。 然后通过返回来指示缺少查询结果。 返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回,而是返回相应的空表示形式。 有关详细信息,请参阅 “存储库查询返回类型”。​​null​​​​null​

可为空性注释

您可以使用 Spring Framework 的可空性注释来表达存储库方法的可空性约束。 它们提供了一种工具友好的方法,并在运行时进行选择加入,如下所示:null

  • @NonNullApi:在包级别用于声明参数和返回值的默认行为分别是既不接受也不生成值。null
  • @NonNull:用于不得使用的参数或返回值(在适用的参数和返回值上不需要)。null@NonNullApi
  • @Nullable:用于可以的参数或返回值。null

Spring 注释是用 JSR305注释(一种休眠但广泛使用的 JSR)进行元注释的。 JSR 305元注解允许工具供应商(如IDEA,Eclipse和Kotlin)以通用方式提供空安全支持,而不必对Spring注解进行硬编码支持。 要启用查询方法的可空性约束的运行时检查,您需要使用 Spring'sin在包级别激活非可空性,如以下示例所示:​​@NonNullApi​​​​package-info.java​

例 18。声明 中的非可空性​​package-info.java​

@org.springframework.lang.NonNullApi
package com.acme;

一旦非 null 默认值到位,存储库查询方法调用将在运行时验证可空性约束。 如果查询结果违反定义的约束,则会引发异常。 当方法将返回但被声明为不可为空(默认值,在存储库所在的包上定义的注释)时,会发生这种情况。 如果要再次选择加入可为空的结果,请有选择地使用单个方法。 使用本节开头提到的结果包装器类型将继续按预期工作:空结果将转换为表示缺席的值。​​null​​​​@Nullable​

以下示例显示了刚才描述的许多技术:

例 19。使用不同的可为空性约束

package com.acme;                                                       

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

User getByEmailAddress(EmailAddress emailAddress);

@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress);

Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress);
}

存储库驻留在我们为其定义了非空行为的包(或子包)中。

当查询未生成结果时引发。

扔一个当手到方法就是。​​EmptyResultDataAccessException​​​​IllegalArgumentException​​​​emailAddress​​​​null​

当查询未生成结果时返回。

也接受作为值。​​null​​​​null​​​​emailAddress​

当查询未生成结果时返回。

扔一个当手到方法就是。​​Optional.empty()​​​​IllegalArgumentException​​​​emailAddress​​​​null​

基于 Kotlin 的存储库中的可空性

Kotlin 将可空性约束的定义融入到语言中。 Kotlin 代码编译为字节码,字节码不通过方法签名表示可空性约束,而是通过编译的元数据来表达可空性约束。 确保在您的项目中包含 JAR,以便能够内省 Kotlin 的可空性约束。 Spring 数据存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:​​kotlin-reflect​

例 20。在 Kotlin 存储库上使用可空性约束

interface UserRepository : Repository<User, String> {

fun findByUsername(username: String): User

fun findByFirstname(firstname: String?): User?
}

该方法将参数和结果定义为不可为空(Kotlin 默认值)。

Kotlin 编译器拒绝传递给该方法的方法调用。如果查询产生空结果,则引发 anis 。​​null​​​​EmptyResultDataAccessException​

此方法接受参数并返回查询未产生结果。​​null​​​​firstname​​​​null​

2.4.8. 流式查询结果

您可以使用 Java 8 作为返回类型以增量方式处理查询方法的结果。 不是将查询结果包装在 中,而是使用特定于数据存储的方法执行流式处理,如以下示例所示:​​Stream<T>​​​​Stream​

例 21。使用 Java 8 流式传输查询结果​​Stream<T>​

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

可能会包装特定于基础数据存储的资源,因此必须在使用后关闭。 您可以使用该方法或使用 Java 7block 手动关闭,如以下示例所示:​​Stream​​​​Stream​​​​close()​​​​try-with-resources​

例 22。在块中使用结果​​Stream<T>​​​​try-with-resources​

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}

并非所有 Spring 数据模块当前都支持返回类型。​​Stream<T>​

2.4.9. 异步查询结果

您可以使用Spring 的异步方法运行功能异步运行存储库查询。 这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中。 异步查询不同于反应式查询,不应混合使用。 有关反应式支持的更多详细信息,请参阅特定于商店的文档。 以下示例显示了许多异步查询:​​TaskExecutor​

@Async
Future<User> findByFirstname(String firstname);

@Async
CompletableFuture<User> findOneByFirstname(String firstname);

@Async
ListenableFuture<User> findOneByLastname(String lastname);

用作返回类型。​​java.util.concurrent.Future​

使用 Java 8 作为返回类型。​​java.util.concurrent.CompletableFuture​

使用 aas 返回类型。​​org.springframework.util.concurrent.ListenableFuture​

2.5. 创建存储库实例

本节介绍如何为定义的存储库接口创建实例和 Bean 定义。 一种方法是使用 Spring 命名空间,该命名空间随支持存储库机制的每个 Spring 数据模块一起提供,尽管我们通常建议使用 Java 配置。

2.5.1.XML 配置

每个 Spring 数据模块都包含一个元素,该元素允许您定义 Spring 为您扫描的基本包,如以下示例所示:​​repositories​

例 23。通过XML启用Spring 数据存储库

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<repositories base-package="com.acme.repositories" />

</beans:beans>

在前面的示例中,指示 Spring 扫描其所有子包以查找接口扩展或其子接口之一。 对于找到的每个接口,基础结构都会注册特定于持久性技术,以创建处理查询方法调用的相应代理。 每个 Bean 都注册在从接口名称派生的 Bean 名称下,因此将注册一个接口。 嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。 该属性允许通配符,以便您可以定义扫描包的模式。​​com.acme.repositories​​​​Repository​​​​FactoryBean​​​​UserRepository​​​​userRepository​​​​base-package​

使用过滤器

缺省情况下,基础结构选取扩展位于配置的基础包下的特定于持久性技术的子接口的每个接口,并为其创建一个 Bean 实例。 但是,您可能希望更精细地控制哪些接口为其创建了 Bean 实例。 为此,请在元素中使用元素。 语义完全等同于 Spring 上下文命名空间中的元素。 有关详细信息,请参阅这些元素的Spring 参考文档。​​Repository​​​​<include-filter />​​​​<exclude-filter />​​​​<repositories />​

例如,要从实例化中排除某些接口作为存储库 Bean,您可以使用以下配置:

例 24。使用排除过滤器元素

<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

前面的示例排除了以实例化结尾的所有接口。​​SomeRepository​

2.5.2. Java 配置

您还可以通过在 Java 配置类上使用特定于存储的注释来触发存储库基础结构。 有关 Spring 容器的基于 Java 的配置的介绍,请参阅Spring 参考文档中的 JavaConfig。​​@Enable${store}Repositories​

启用 Spring 数据存储库的示例配置类似于以下内容:

例 25。基于注释的存储库配置示例

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}

前面的示例使用特定于 JPA 的注释,您将根据实际使用的存储模块对其进行更改。 这同样适用于thebean的定义。 请参阅涵盖特定于商店的配置的部分。​​EntityManagerFactory​

2.5.3. 独立使用

您还可以在 Spring 容器之外使用存储库基础架构,例如,在 CDI 环境中。 你的类路径中仍然需要一些 Spring 库,但通常,你也可以以编程方式设置存储库。 提供存储库支持的 Spring 数据模块附带了您可以使用的特定于持久性技术的模块,如下所示:​​RepositoryFactory​

例 26。存储库工厂的独立使用

RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

2.6. Spring 数据存储库的自定义实现

本节介绍存储库自定义以及片段如何形成复合存储库。

当查询方法需要不同的行为或无法通过查询派生实现时,需要提供自定义实现。 Spring 数据存储库允许您提供自定义存储库代码,并将其与通用的 CRUD 抽象和查询方法功能集成。

2.6.1. 自定义单个仓库

要使用自定义功能丰富存储库,您必须首先定义片段接口和自定义功能的实现,如下所示:

例 27。自定义存储库功能的界面

interface CustomizedUserRepository {
void someCustomMethod(User user);
}

例 28。实现自定义存储库功能

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

public void someCustomMethod(User user) {
// Your custom implementation
}
}

与片段接口对应的类名中最重要的部分是后缀。​​Impl​

实现本身不依赖于 Spring 数据,可以是常规的 Spring bean。 因此,您可以使用标准的依赖注入行为来注入对其他 Bean 的引用(例如 a)、参与方面等。​​JdbcTemplate​

然后,您可以让存储库接口扩展片段接口,如下所示:

例 29。对存储库界面的更改

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

// Declare query methods here
}

使用存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。

Spring 数据存储库是通过使用形成存储库组合的片段来实现的。 片段是基本存储库、功能方面(如QueryDsl)和自定义接口及其实现。 每次向存储库界面添加接口时,都会通过添加片段来增强组合。 基本存储库和存储库方面实现由每个 Spring 数据模块提供。

以下示例显示了自定义接口及其实现:

例 30。片段及其实现

interface HumanRepository {
void someHumanMethod(User user);
}

class HumanRepositoryImpl implements HumanRepository {

public void someHumanMethod(User user) {
// Your custom implementation
}
}

interface ContactRepository {

void someContactMethod(User user);

User anotherContactMethod(User user);
}

class ContactRepositoryImpl implements ContactRepository {

public void someContactMethod(User user) {
// Your custom implementation
}

public User anotherContactMethod(User user) {
// Your custom implementation
}
}

以下示例显示了扩展的自定义存储库的接口:​​CrudRepository​

例 31。对存储库界面的更改

interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

// Declare query methods here
}

存储库可以由多个自定义实现组成,这些实现按其声明顺序导入。 自定义实现的优先级高于基本实现和存储库方面。 此排序允许您覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。 存储库片段不限于在单个存储库界面中使用。 多个存储库可以使用片段界面,允许您跨不同存储库重用自定义项。

以下示例显示了存储库片段及其实现:

例 32。片段覆盖​​save(…)​

interface CustomizedSave<T> {
<S extends T> S save(S entity);
}

class CustomizedSaveImpl<T> implements CustomizedSave<T> {

public <S extends T> S save(S entity) {
// Your custom implementation
}
}

以下示例显示了使用上述存储库片段的存储库:

例 33。自定义存储库接口

interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置

如果使用命名空间配置,存储库基础结构会尝试通过扫描找到存储库的包下方的类来自动检测自定义实现片段。 这些类需要遵循将命名空间元素的属性附加到片段接口名称的命名约定。 此后缀默认为 。 以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:​​repository-impl-postfix​​​​Impl​

例 34。配置示例

<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​​​​customizedUserRepositoryImpl​​​​CustomizedUserRepository​​​​Impl​

例 35。解决不明确的实现

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
}

如果您注释接口,则 bean 名称 plusthen 与为存储库实现定义的名称匹配,并且使用它代替第一个。​​UserRepository​​​​@Component("specialCustom")​​​​Impl​​​​com.acme.impl.two​

手动接线

如果您的自定义实现仅使用基于注释的配置和自动连线,则上述方法效果很好,因为它被视为任何其他 Spring Bean。 如果您的实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础结构按名称引用手动定义的 Bean 定义,而不是自己创建一个。 以下示例演示如何手动连接自定义实现:

例 36。自定义实现的手动接线

<repositories base-package="com.acme.repository" />

<beans:bean class="…">
<!-- further configuration -->
</beans:bean>

2.6.2. 自定义基础仓库

当您想要自定义基本存储库行为以使所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:

例 37。自定义存储库基类

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;
}

@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}

该类需要具有特定于存储的存储库工厂实现使用的超类的构造函数。 如果存储库基类有多个构造函数,请覆盖采用存储特定基础结构对象(例如模板类)的构造函数。​​EntityInformation​​​​EntityManager​

最后一步是使 Spring 数据基础架构知道自定义的存储库基类。 在 Java 配置中,您可以使用注释的属性来执行此操作,如以下示例所示:​​repositoryBaseClass​​​​@Enable${store}Repositories​

例 38。使用 JavaConfig 配置定制存储库基类

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

XML 命名空间中提供了相应的属性,如以下示例所示:

例 39。使用 XML 配置自定义存储库基类

<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />

2.7. 从聚合根发布事件

存储库管理的实体是聚合根。 在域驱动设计应用程序中,这些聚合根通常发布域事件。 Spring Data 提供了一个注释,称为您可以在聚合根的方法上使用该注释,以使该发布尽可能简单,如以下示例所示:​​@DomainEvents​

例 40。从聚合根公开域事件

class AnAggregateRoot {

@DomainEvents
Collection<Object> domainEvents() {
// … return events you want to get published here
}

@AfterDomainEventPublication
void callbackMethod() {
// … potentially clean up domain events list
}
}

使用的方法可以返回单个事件实例或事件集合。

它绝不能接受任何论据。​​@DomainEvents​

发布所有事件后,我们有一个注释方法。

您可以使用它来潜在地清理要发布的事件列表(以及其他用途)。​​@AfterDomainEventPublication​

每次调用 Spring 数据存储库或方法之一时都会调用这些方法。​​save(…)​​​​saveAll(…)​​​​delete(…)​​​​deleteAll(…)​

2.8. 弹簧数据扩展

本节记录了一组 Spring 数据扩展,这些扩展允许在各种上下文中使用 Spring 数据。 目前,大多数集成都是针对Spring MVC的。

2.8.1. 查询扩展

Querydsl是一个框架,它支持通过其流畅的API构造静态类型的类似SQL的查询。

几个 Spring 数据模块提供与 Querydsl 的集成,如以下示例所示:​​QuerydslPredicateExecutor​

例 41。QuerydslPredicateExecutor interface

public interface QuerydslPredicateExecutor<T> {

Optional<T> findById(Predicate predicate);

Iterable<T> findAll(Predicate predicate);

long count(Predicate predicate);

boolean exists(Predicate predicate);

// … more functionality omitted.
}

查找并返回与 匹配的单个实体。​​Predicate​

查找并返回与 匹配的所有实体。​​Predicate​

返回与 匹配的实体数。​​Predicate​

返回与实体匹配的实体是否存在。​​Predicate​

要使用 Querydsl 支持,请扩展存储库接口,如以下示例所示:​​QuerydslPredicateExecutor​

例 42。存储库上的 querydsl 集成

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

前面的示例允许您使用 Querydslinstances 编写类型安全的查询,如以下示例所示:​​Predicate​

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

2.8.2. 网页支持

支持存储库编程模型的 Spring 数据模块附带了各种 Web 支持。 与Web相关的组件要求Spring MVC JAR位于类路径上。 其中一些甚至提供与Spring HATEOAS的集成。 通常,集成支持是通过在 JavaConfig 配置类中使用注释来启用的,如以下示例所示:​​@EnableSpringDataWebSupport​

例 43。启用 Spring 数据网络支持

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

注释注册了一些组件。 我们将在本节后面讨论这些内容。 它还检测类路径上的Spring HATEOAS,并为其注册集成组件(如果存在)。​​@EnableSpringDataWebSupport​

或者,如果使用 XML 配置,请注册 要么 or Spring bean,如以下示例所示 (for):​​SpringDataWebConfiguration​​​​HateoasAwareSpringDataWebConfiguration​​​​SpringDataWebConfiguration​

例 44。在XML中启用Spring Data Web支持

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本网络支持

上一节中显示的配置注册了一些基本组件:

  • A使用DomainClassConverter类让Spring MVC 从请求参数或路径变量解析存储库管理的域类的实例。
  • HandlerMethodArgumentResolver实现,让 Spring MVC 从请求参数解析和实例。PageableSort
  • 杰克逊模块,用于反序列化类型,或存储特定的类型,具体取决于所使用的 Spring 数据模块。PointDistance
使用类​​DomainClassConverter​

该类允许您直接在Spring MVC控制器方法签名中使用域类型,这样您就不需要通过存储库手动查找实例,如以下示例所示:​​DomainClassConverter​

例 45。在方法签名中使用域类型的Spring MVC控制器

@Controller
@RequestMapping("/users")
class UserController {

@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {

model.addAttribute("user", user);
return "userForm";
}
}

该方法直接接收实例,无需进一步查找。 可以通过让Spring MVC首先将路径变量转换为域类的类型,并最终通过调用为域类型注册的存储库实例来访问实例来解决该实例。​​User​​​​id​​​​findById(…)​

目前,存储库必须实现才有资格被发现进行转换。​​CrudRepository​

用于可分页和排序的处理程序方法参数解析器

上一节中显示的配置片段还注册了 aas 以及 的实例。 注册启用 sandas 有效控制器方法参数,如以下示例所示:​​PageableHandlerMethodArgumentResolver​​​​SortHandlerMethodArgumentResolver​​​​Pageable​​​​Sort​

例 46。使用可分页作为控制器方法参数

@Controller
@RequestMapping("/users")
class UserController {

private final UserRepository repository;

UserController(UserRepository repository) {
this.repository = repository;
}

@RequestMapping
String showUsers(Model model, Pageable pageable) {

model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}

前面的方法签名导致Spring MVC尝试使用以下默认配置从请求参数派生实例:​​Pageable​

表 1.为实例评估的请求参数​​Pageable​

​page​

要检索的页面。0 索引,默认为 0。

​size​

要检索的页面的大小。默认值为 20。

​sort​

应按格式排序的属性。默认排序方向为区分大小写的升序。如果要切换方向或区分大小写,请使用多个参数,例如。​​property,property(,ASC|DESC)(,IgnoreCase)​​​​sort​​​​?sort=firstname&sort=lastname,asc&sort=city,ignorecase​

要定制此行为,请分别注册实现接口或接口的 Bean。 调用 Itsmethod,允许您更改设置,如以下示例所示:​​PageableHandlerMethodArgumentResolverCustomizer​​​​SortHandlerMethodArgumentResolverCustomizer​​​​customize()​

@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}

如果设置现有属性不足以满足您的目的,请扩展启用 HATEOAS 的等效项,覆盖理论方法,并导入自定义配置文件,而不是使用注释。​​MethodArgumentResolver​​​​SpringDataWebConfiguration​​​​pageableResolver()​​​​sortResolver()​​​​@Enable​

如果您需要从请求中解析多个实例(例如,对于多个表),则可以使用 Spring'sannotation 来区分彼此。 然后,请求参数必须带有前缀。 下面的示例演示生成的方法签名:​​Pageable​​​​Sort​​​​@Qualifier​​​​${qualifier}_​

String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }

您必须填充,等等。​​thing1_page​​​​thing2_page​

默认传递给方法等效于 a,但您可以通过对参数使用注释来自定义它。​​Pageable​​​​PageRequest.of(0, 20)​​​​@PageableDefault​​​​Pageable​

对可分页的超媒体支持

Spring HATEOAS 附带了一个表示模型类 (),它允许使用必要的元数据和链接来丰富实例的内容,让客户端轻松浏览页面。 ato ais 的转换是通过 Spring HATEOAS接口的实现完成的,称为。 下面的示例演示如何使用 aas 控制器方法参数:​​PagedResources​​​​Page​​​​Page​​​​Page​​​​PagedResources​​​​ResourceAssembler​​​​PagedResourcesAssembler​​​​PagedResourcesAssembler​

例 47。使用 PagedResourcesAssembler 作为控制器方法参数

@Controller
class PersonController {

@Autowired PersonRepository repository;

@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {

Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}

启用配置(如前面的示例所示)允许将 thebe 用作控制器方法参数。 呼叫它具有以下效果:​​PagedResourcesAssembler​​​​toResources(…)​

  • 的内容成为实例的内容。PagePagedResources
  • 对象被附加一个实例,并填充来自底层的信息。PagedResourcesPageMetadataPagePageRequest
  • 附上可能获取链接,具体取决于页面的状态。 链接指向方法映射到的 URI。 添加到方法的分页参数与设置匹配,以确保以后可以解析链接。PagedResourcesprevnextPageableHandlerMethodArgumentResolver

假设数据库中有 30 个实例。 您现在可以触发请求 () 并查看类似于以下内容的输出:​​Person​​​​GET http://localhost:8080/persons​

{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}

汇编程序生成了正确的 URI,并且还选取了默认配置,以将参数解析为 afor 即将到来的请求。 这意味着,如果更改该配置,链接将自动遵循更改。 默认情况下,汇编程序指向在其中调用它的控制器方法,但您可以通过传递用作构建分页链接的基础的自定义来自定义该方法,这会重载该方法。​​Pageable​​​​Link​​​​PagedResourcesAssembler.toResource(…)​

弹簧数据杰克逊模块

核心模块,以及一些特定于商店的模块,附带了一组杰克逊模块,用于Spring Data域使用的类似类型。
一旦启用了Web 支持并且可用,这些模块就会被导入。​​org.springframework.data.geo.Distance​​​​org.springframework.data.geo.Point​​​​com.fasterxml.jackson.databind.ObjectMapper​

在初始化期间,就像 一样,由基础结构拾取,以便声明的 Jackson 可用。​​SpringDataJacksonModules​​​​SpringDataJacksonConfiguration​​​​com.fasterxml.jackson.databind.Module​​​​ObjectMapper​

以下域类型的数据绑定混合由通用基础结构注册。

org.springframework.data.geo.Distance
org.springframework.data.geo.Point
org.springframework.data.geo.Box
org.springframework.data.geo.Circle
org.springframework.data.geo.Polygon


单个模块可以提供额外的。
有关更多详细信息,请参阅商店特定部分。​​​SpringDataJacksonModules​


网页数据绑定支持

您可以使用 Spring 数据投影(在 [投影]中描述)通过使用 JSONPath 表达式(需要Jayway JsonPath或XPath表达式(需要XmlBeam)来绑定传入的请求有效负载,如以下示例所示:

例 48。使用 JSONPath 或 XPath 表达式的 HTTP 有效负载绑定

@ProjectedPayload
public interface UserPayload {

@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();

@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}

您可以使用前面示例中所示的类型作为 Spring MVC 处理程序方法参数,也可以使用 on 的方法之一。 前面的方法声明将尝试在给定文档中的任何地方查找。 XML 查找在传入文档的顶层执行。 它的 JSON 变体尝试*优先,但如果前者不返回值,也会尝试嵌套在子文档中。 这样,可以轻松缓解源文档结构中的更改,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。​​ParameterizedTypeReference​​​​RestTemplate​​​​firstname​​​​lastname​​​​lastname​​​​lastname​​​​user​

支持嵌套投影,如 [投影] 中所述。 如果该方法返回复杂的非接口类型,则使用 Jacksonis 映射最终值。​​ObjectMapper​

对于Spring MVC,一旦激活,就会自动注册必要的转换器,并且所需的依赖项在类路径上可用。 如需使用 ,请注册 (JSON) 或手动注册。​​@EnableSpringDataWebSupport​​​​RestTemplate​​ProjectingJackson2HttpMessageConverterXmlBeamHttpMessageConverter

有关更多信息,请参阅规范的 Spring 数据示例存储库中的Web 投影示例。

查询网络支持

对于那些具有QueryDSL集成的存储,可以从查询字符串中包含的属性派生查询。​​Request​

请考虑以下查询字符串:

?firstname=Dave&lastname=Matthews

给定前面示例中的对象,可以使用 将查询字符串解析为以下值,如下所示:​​User​​​​QuerydslPredicateArgumentResolver​

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

当在类路径上找到 Querydsl 时,将自动启用该功能。​​@EnableSpringDataWebSupport​

将 ato 添加到方法签名提供了一个即用型,您可以使用 来运行。​​@QuerydslPredicate​​​​Predicate​​​​QuerydslPredicateExecutor​

类型信息通常从方法的返回类型中解析。 由于该信息不一定与域类型匹配,因此最好使用 的属性。​​root​​​​QuerydslPredicate​

下面的示例演示如何在方法中使用签名:​​@QuerydslPredicate​

@Controller
class UserController {

@Autowired UserRepository repository;

@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

model.addAttribute("users", repository.findAll(predicate, pageable));

return "index";
}
}

将查询字符串参数解析为匹配对象。​​Predicate​​​​User​

默认绑定如下所示:

  • ​Object​​在简单属性上。eq
  • ​Object​​在集合上像属性一样。contains
  • ​Collection​​在简单属性上。in

您可以通过属性 ofor 自定义这些绑定,方法是使用 Java 8 并将方法添加到存储库接口,如下所示:​​bindings​​​​@QuerydslPredicate​​​​default methods​​​​QuerydslBinderCustomizer​

interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>,
QuerydslBinderCustomizer<QUser> {

@Override
default void customize(QuerydslBindings bindings, QUser user) {

bindings.bind(user.username).first((path, value) -> path.contains(value))
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value));
bindings.excluding(user.password);
}
}

​QuerydslPredicateExecutor​​​提供对特定查找器方法的访问。​​Predicate​

​QuerydslBinderCustomizer​​​在存储库界面上定义的自动拾取和快捷方式。​​@QuerydslPredicate(bindings=…)​

将属性的绑定定义为简单绑定。​​username​​​​contains​

将属性的默认绑定定义为不区分大小写的匹配项。​​String​​​​contains​

从解析中排除该属性。​​password​​​​Predicate​

2.8.3. 仓库填充器

如果您使用 Spring JDBC 模块,您可能熟悉对填充 SQL 脚本的支持。 类似的抽象在存储库级别可用,尽管它不使用 SQL 作为数据定义语言,因为它必须独立于存储。 因此,填充器支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。​​DataSource​

假设您有一个包含以下内容的文件:​​data.json​

例 49。在 JSON 中定义的数据

[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]

您可以使用 Spring 数据共享中提供的存储库命名空间的填充器元素来填充存储库。 若要将上述数据填充到 ur,请声明类似于以下内容的填充器:​​PersonRepository​

例 50。声明杰克逊存储库填充器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">

<repository:jackson2-populator locations="classpath:data.json" />

</beans>

前面的声明会导致 Jackson 读取和反序列化该文件。​​data.json​​​​ObjectMapper​

JSON 对象解组的类型是通过检查 JSON 文档的属性来确定的。 基础结构最终会选择适当的存储库来处理反序列化的对象。​​_class​

要改用 XML 来定义存储库应填充的数据,您可以使用 theelement。 您可以将其配置为使用 Spring OXM 中可用的 XML 编组选项之一。有关详细信息,请参阅Spring 参考文档。 以下示例显示如何使用 JAXB 取消编组存储库填充器:​​unmarshaller-populator​

例 51。声明解组存储库填充器(使用 JAXB)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">

<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />

<oxm:jaxb2-marshaller contextPath="com.acme" />

</beans>

Apache Solr 的 Spring Data (数据)

3. Solr 存储库

本章介绍 Solr 存储库实现的详细信息。

3.1. 弹簧命名空间

Spring Data Solr 模块包含一个自定义命名空间,该命名空间允许定义存储库 bean,并具有用于实例化的元素。​​SolrClient​

使用该元素查找 Spring 数据存储库,如创建存储库实例中所述。​​repositories​

以下示例显示了如何设置使用 Spring Data Solr 命名空间的 Solr 存储库:

例 52。使用命名空间设置 Solr 存储库

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/solr
https://www.springframework.org/schema/data/solr/spring-solr.xsd">

<solr:repositories base-package="com.acme.repositories" />
</beans>

使用 theorelement 在上下文中注册一个实例。​​solr-server​​​​embedded-solr-server​​​​SolrClient​

以下示例演示如何为 HTTP 设置 Solr 客户端:

例 53.使用命名空间​​HttpSolrClient​

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/solr
https://www.springframework.org/schema/data/solr/spring-solr.xsd">

<solr:solr-client url="https://locahost:8983/solr" />
</beans>

以下示例演示如何设置负载平衡 Solr 客户端:

例 54.使用命名空间​​LBSolrClient​

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/solr
https://www.springframework.org/schema/data/solr/spring-solr.xsd">

<solr:solr-client url="https://locahost:8983/solr,http://localhost:8984/solr" />
</beans>

以下示例演示如何设置嵌入式 Solr 服务器:

例 55。使用命名空间的嵌入式 SolrServer

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:solr="http://www.springframework.org/schema/data/solr"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/solr
https://www.springframework.org/schema/data/solr/spring-solr.xsd">

<solr:embedded-solr-server solrHome="classpath:com/acme/solr" />
</beans>

3.2. 基于注释的配置

Spring Data Solr 存储库支持可以通过 XML 命名空间激活,也可以通过 Java 配置使用注释来激活。

以下示例显示了如何使用 Java 配置设置 Solr 存储库:

例 56。使用 Java 配置的 Spring Data Solr 存储库

@Configuration
@EnableSolrRepositories
class ApplicationConfig {

@Bean
public SolrClient solrClient() {
EmbeddedSolrServerFactory factory = new EmbeddedSolrServerFactory("classpath:com/acme/solr");
return factory.getSolrServer();
}

@Bean
public SolrOperations solrTemplate() {
return new SolrTemplate(solrClient());
}
}

上述配置设置了一个,由 使用。Spring Data Solr 存储库是使用注释激活的,注释本质上具有与 XML 命名空间相同的属性。如果未配置基础包,则使用配置类所在的包。​​EmbeddedSolrServer​​​​SolrTemplate​​​​@EnableSolrRepositories​

3.3. 使用 CDI 设置 Solr 存储库

您还可以使用 CDI 设置 Spring Data Solr 存储库,如以下示例所示:

例 57。使用 Java 配置的 Spring Data Solr 存储库

class SolrTemplateProducer {

@Produces
@ApplicationScoped
public SolrOperations createSolrTemplate() {
return new SolrTemplate(new EmbeddedSolrServerFactory("classpath:com/acme/solr"));
}
}

class ProductService {

private ProductRepository repository;

public Page<Product> findAvailableProductsByName(String name, Pageable pageable) {
return repository.findByAvailableTrueAndNameStartingWith(name, pageable);
}

@Inject
public void setRepository(ProductRepository repository) {
this.repository = repository;
}
}

3.4. 交易支持

Solr对服务器级别事务的支持意味着自上次提交,优化或回滚以来的创建,更新和删除操作在服务器上排队,并作为一个组提交,优化或回滚。Spring Data Solr 存储库参与 Spring 托管事务,并在完成后提交或回滚更改。

下面的示例演示如何使用注释来定义事务(在本例中为 save):​​@Transactional​

@Transactional
public Product save(Product product) {
Product savedProduct = jpaRepository.save(product);
solrRepository.save(savedProduct);
return savedProduct;
}

3.5. 查询方法

本节介绍如何使用 Java 类中的方法创建查询。

3.5.1. 查询查找策略

Solr 模块支持手动定义查询,或者从方法名称派生查询。​​String​

目前没有 QueryDSL 支持。

声明的查询

从方法名称派生查询并不总是足够的,并且可能导致方法名称不可读。在这种情况下,您可以使用 Solr 命名查询(请参阅“使用命名查询”)或使用注释(请参阅“使用@Query注释”)。@Query

3.5.2. 查询创建

通常,Solr 的查询创建机制的工作方式如查询方法中所述。以下示例显示了 Solr 查询方法:

例 58.从方法名称创建查询

public interface ProductRepository extends Repository<Product, String> {
List<Product> findByNameAndPopularity(String name, Integer popularity);
}

前面的示例转换为以下 Solr 查询:

q=name:?0 AND popularity:?1

下表描述了 Solr 支持的关键字:

Table 2. Supported keywords inside method names

关键词

样本

Solr 查询字符串

​And​

​findByNameAndPopularity​

​q=name:?0 AND popularity:?1​

​Or​

​findByNameOrPopularity​

​q=name:?0 OR popularity:?1​

​Is​

​findByName​

​q=name:?0​

​Not​

​findByNameNot​

​q=-name:?0​

​IsNull​

​findByNameIsNull​

​q=-name:[* TO *]​

​IsNotNull​

​findByNameIsNotNull​

​q=name:[* TO *]​

​Between​

​findByPopularityBetween​

​q=popularity:[?0 TO ?1]​

​LessThan​

​findByPopularityLessThan​

​q=popularity:[* TO ?0}​

​LessThanEqual​

​findByPopularityLessThanEqual​

​q=popularity:[* TO ?0]​

​GreaterThan​

​findByPopularityGreaterThan​

​q=popularity:{?0 TO *]​

​GreaterThanEqual​

​findByPopularityGreaterThanEqual​

​q=popularity:[?0 TO *]​

​Before​

​findByLastModifiedBefore​

​q=last_modified:[* TO ?0}​

​After​

​findByLastModifiedAfter​

​q=last_modified:{?0 TO *]​

​Like​

​findByNameLike​

​q=name:?0*​

​NotLike​

​findByNameNotLike​

​q=-name:?0*​

​StartingWith​

​findByNameStartingWith​

​q=name:?0*​

​EndingWith​

​findByNameEndingWith​

​q=name:*?0​

​Containing​

​findByNameContaining​

​q=name:*?0*​

​Matches​

​findByNameMatches​

​q=name:?0​

​In​

​findByNameIn(Collection<String> names)​

​q=name:(?0… )​

​NotIn​

​findByNameNotIn(Collection<String> names)​

​q=-name:(?0… )​

​Within​

​findByStoreWithin(Point, Distance)​

​q={!geofilt pt=?0.latitude,?0.longitude sfield=store d=?1}​

​Near​

​findByStoreNear(Point, Distance)​

​q={!bbox pt=?0.latitude,?0.longitude sfield=store d=?1}​

​Near​

​findByStoreNear(Box)​

​q=store[?0.start.latitude,?0.start.longitude TO ?0.end.latitude,?0.end.longitude]​

​True​

​findByAvailableTrue​

​q=inStock:true​

​False​

​findByAvailableFalse​

​q=inStock:false​

​OrderBy​

​findByAvailableTrueOrderByNameDesc​

​q=inStock:true&sort=name desc​

集合类型可以与“喜欢”、“不喜欢”、“开始为”、“结束于”和“包含”一起使用。

Page<Product> findByNameLike(Collection<String> name);

3.5.3. 使用注解​​@Query​

使用命名查询(请参阅“使用命名查询”)为实体声明查询是一种有效的方法,适用于少量查询。由于查询本身与执行它们的 Java 方法相关联,因此您实际上可以使用 Spring Data Solrannotation 直接绑定它们。下面的示例使用注释来声明查询:​​@Query​​​​@Query​

例 59。使用注释在方法上声明查询。​​@Query​

public interface ProductRepository extends SolrRepository<Product, String> {
@Query("inStock:?0")
List<Product> findByAvailable(Boolean available);
}

3.5.4. 使用命名查询

命名查询可以保存在属性文件中,并连接到相应的方法。您应该记住“查询查找策略”或使用中描述的命名约定。下面的示例演示如何在属性文件中声明名称查询:​​@Query​

例 60。在属性文件中声明命名查询

Product.findByNamedQuery=popularity:?0
Product.findByName=name:?0

下面的示例使用前面示例中声明的命名查询 () 之一:​​findByName​

public interface ProductRepository extends SolrCrudRepository<Product, String> {

List<Product> findByNamedQuery(Integer popularity);

@Query(name = "Product.findByName")
List<Product> findByAnnotatedNamedQuery(String name);

}

3.6. 文档映射

虽然SolrJ中已经支持实体映射,但Spring Data Solr附带了自己的映射机制(在下一节中描述)。

​DocumentObjectBinder​​​具有优越的性能。因此,如果您不需要客户映射,我们建议您使用它。您可以通过注册切换 toin。​​DocumentObjectBinder​​​​SolrJConverter​​​​SolrTemplate​

3.6.1. 对象映射基础

本节介绍了 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不变性的基础知识。 请注意,本节仅适用于不使用底层数据存储的对象映射(如 JPA)的 Spring 数据模块。 此外,请务必查阅特定于存储的部分,了解特定于存储的对象映射,例如索引、自定义列或字段名称等。

Spring 数据对象映射的核心职责是创建域对象的实例,并将存储本机数据结构映射到这些实例上。 这意味着我们需要两个基本步骤:

  1. 使用公开的构造函数之一创建实例。
  2. 实例填充以具体化所有公开的属性。
对象创建

Spring Data 自动尝试检测持久实体的构造函数,以用于具体化该类型的对象。 解析算法的工作原理如下:

  1. 如果只有一个构造函数,则使用它。
  2. 如果有多个构造函数,并且只有一个被批注,则使用它。@PersistenceConstructor
  3. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。

值解析假定构造函数参数名称与实体的属性名称匹配,即解析将像要填充属性一样执行,包括映射中的所有自定义(不同的数据存储列或字段名称等)。 这还需要类文件中可用的参数名称信息或构造函数上存在的枚举注释。​​@ConstructorProperties​

值解析可以通过使用特定于商店的SpEL表达式使用Spring Framework的值注释来自定义。 有关更多详细信息,请参阅商店特定映射部分。​​@Value​

对象创建内部

为了避免反射的开销,Spring Data 对象创建默认使用运行时生成的工厂类,该工厂类将直接调用域类构造函数。 即对于此示例类型:

class Person {
Person(String firstname, String lastname) { … }
}

我们将在运行时创建一个语义等同于此工厂类的工厂类:

class PersonObjectInstantiator implements ObjectInstantiator {

Object newInstance(Object... args) {
return new Person((String) args[0], (String) args[1]);
}
}

这为我们提供了大约 10% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:

  • 它不能是私有类
  • 它不能是非静态内部类
  • 它不能是 CGLib 代理类
  • Spring Data 要使用的构造函数不得是私有的

如果这些条件中的任何一个匹配,Spring 数据将通过反射回退到实体实例化。

物业人口

创建实体的实例后,Spring Data 将填充该类的所有剩余持久属性。 除非已由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。 之后,将在实体实例上设置构造函数尚未填充的所有非瞬态属性。 为此,我们使用以下算法:

  1. 如果属性是不可变的,但公开了 amethod(见下文),我们使用该方法创建一个具有新属性值的新实体实例。with…with…
  2. 如果定义了属性访问(即通过 getter 和 setter 的访问),我们将调用 setter 方法。
  3. 如果属性是可变的,我们直接设置字段。
  4. 如果属性是不可变的,我们将使用持久性操作要使用的构造函数(请参阅对象创建)来创建实例的副本。
  5. 默认情况下,我们直接设置字段值。

属性人口内部

与对象构造中的优化类似,我们还使用 Spring 数据运行时生成的访问器类与实体实例进行交互。

class Person {

private final Long id;
private String firstname;
private @AccessType(Type.PROPERTY) String lastname;

Person() {
this.id = null;
}

Person(Long id, String firstname, String lastname) {
// Field assignments
}

Person withId(Long id) {
return new Person(id, this.firstname, this.lastame);
}

void setLastname(String lastname) {
this.lastname = lastname;
}
}

例 61。生成的属性访问器

class PersonPropertyAccessor implements PersistentPropertyAccessor {

private static final MethodHandle firstname;

private Person person;

public void setProperty(PersistentProperty property, Object value) {

String name = property.getName();

if ("firstname".equals(name)) {
firstname.invoke(person, (String) value);
} else if ("id".equals(name)) {
this.person = person.withId((Long) value);
} else if ("lastname".equals(name)) {
this.person.setLastname((String) value);
}
}
}

PropertyAccessor 保存基础对象的可变实例。这是为了启用其他不可变属性的突变。

默认情况下,Spring 数据使用字段访问来读取和写入属性值。根据字段的可见性规则,用于与字段进行交互。​​private​​​​MethodHandles​

该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。调用创建一个新对象。所有后续突变都将发生在新实例中,而之前的突变保持不变。​​withId(…)​​​​withId(…)​​​​Person​

使用属性访问允许直接调用方法,而无需使用。​​MethodHandles​

这为我们提供了大约 25% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:

  • 类型不得驻留在默认值或包下。java
  • 类型及其构造函数必须是public
  • 内部类的类型必须是。static
  • 使用的 Java 运行时必须允许在原始文件中声明类。Java 9 及更高版本施加了某些限制。ClassLoader

默认情况下,Spring Data 尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的属性访问器。

让我们看一下以下实体:

例 62。示例实体

class Person {

private final @Id Long id;
private final String firstname, lastname;
private final LocalDate birthday;
private final int age;

private String comment;
private @AccessType(Type.PROPERTY) String remarks;

static Person of(String firstname, String lastname, LocalDate birthday) {

return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}

Person(Long id, String firstname, String lastname, LocalDate birthday, int age) {

this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}

Person withId(Long id) {
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}

void setRemarks(String remarks) {
this.remarks = remarks;
}
}

标识符属性是最终的,但在构造函数中设置为。

该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。创建新实例时,原始实例保持不变。相同的模式通常应用于存储管理的其他属性,但可能必须更改持久性操作。wither 方法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,设置属性将转换为创建应用了新标识符值的新实例。​​null​​​​withId(…)​​​​Person​

和属性是普通的不可变属性,可能通过 getter 公开。​​firstname​​​​lastname​

属性是不可变的,但派生自属性。

按照所示的设计,数据库值将胜过默认值,因为 Spring Data 使用唯一声明的构造函数。即使意图是首选计算,重要的是此构造函数也采用 as 参数(可能忽略它),否则属性填充步骤将尝试设置 age 字段并失败,因为它是不可变的并且不存在任何方法。​​age​​​​birthday​​​​age​​​​with…​

属性是可变的,通过直接设置其字段来填充。​​comment​

属性是可变的,并通过直接设置 thefield 或通过调用 setter 方法来填充​​remarks​​​​comment​

该类公开用于创建对象的工厂方法和构造函数。

这里的核心思想是使用工厂方法而不是其他构造函数,以避免通过构造函数消除歧义的需要。相反,属性的默认值在工厂方法中处理。​​@PersistenceConstructor​

一般性建议
  • 尝试坚持使用不可变对象 - 不可变对象很容易创建,因为具体化对象只需调用其构造函数即可。 此外,这可以避免域对象充斥着允许客户端代码操作对象状态的 setter 方法。 如果需要这些,最好使它们受到包保护,以便只能由有限数量的共存类型调用它们。 仅构造函数具体化比属性填充快 30%。
  • 提供全参数构造函数 — 即使不能或不想将实体建模为不可变值,提供将实体的所有属性(包括可变属性)作为参数的构造函数仍然有价值,因为这允许对象映射跳过属性填充以获得最佳性能。
  • 使用工厂方法而不是重载的构造函数来避免​​@PersistenceConstructor​​ — 由于需要全参数构造函数来实现最佳性能,我们通常希望公开更多特定于应用程序用例的构造函数,这些构造函数省略了自动生成的标识符等内容。 这是一种既定模式,而是使用静态工厂方法来公开 all-args 构造函数的这些变体。
  • 确保遵守允许使用生成的实例化器和属性访问器类的约束
  • 对于要生成的标识符,仍将最终字段与全参数持久性构造函数(首选)或​​with​​...方法
  • 使用 Lombok 避免样板代码 — 由于持久性操作通常需要构造函数获取所有参数,因此它们的声明变成了对字段分配的繁琐重复,而使用 Lombok 可以最好地避免这些参数。@AllArgsConstructor
Kotlin 支持

Spring Data 调整了 Kotlin 的细节,以允许对象创建和更改。

Kotlin 对象创建

Kotlin 类支持实例化,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。 请考虑以下类:​​data​​​​Person​

data class Person(val id: String, val name: String)

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义此类并对其进行注释以指示构造函数首选项:​​@PersistenceConstructor​

data class Person(var id: String, val name: String) {

@PersistenceConstructor
constructor(id: String) : this(id, "unknown")
}

Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。 当 Spring Data 检测到参数默认值的构造函数时,如果数据存储不提供值(或只是返回),则这些参数将保留为不存在,以便 Kotlin 可以应用参数默认值。请考虑以下应用参数默认值的类​​null​​​​name​

data class Person(var id: String, val name: String = "unknown")

每次参数不是结果的一部分或其值是时,则默认为。​​name​​​​null​​​​name​​​​unknown​

Kotlin 数据类的属性填充

在 Kotlin 中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。 请考虑以下类:​​data​​​​Person​

data class Person(val id: String, val name: String)

此类实际上是不可变的。 它允许在 Kotlin 生成方法时创建新实例,该方法创建新的对象实例,从现有对象复制所有属性值,并将作为参数提供的属性值应用于该方法。​​copy(…)​

3.6.2. ​​MappingSolrConverter​

​MappingSolrConverter​​允许您为您的兰达斯以及嵌套在 Bean 中的其他类型的注册自定义转换器。转换器不是 100% 兼容的,并且必须添加 withs 以忽略写入 Solr 的字段。下面的示例映射文档中的多个域:​​SolrDocument​​​​SolrInputDocument​​​​DocumentObjectBinder​​​​@Indexed​​​​readonly=true​

例 63。示例文档映射

public class Product {
@Field
private String simpleProperty;

@Field("somePropertyName")
private String namedPropery;

@Field
private List<String> listOfValues;

@Indexed(readonly = true)
@Field("property_*")
private List<String> ignoredFromWriting;

@Field("mappedField_*")
private Map<String, List<String>> mappedFieldValues;

@Dynamic
@Field("dynamicMappedField_*")
private Map<String, String> dynamicMappedFieldValues;

@Field
private GeoLocation location;

}

下表描述了可以映射的属性:​​MappingSolrConverter​

财产

写入映射

简单属性

​<field name="simpleProperty">value</field>​

命名属性

​<field name="somePropertyName">value</field>​

列表值

​<field name="listOfValues">value 1</field>​​​ ​​<field name="listOfValues">value 2</field>​​​ ​​<field name="listOfValues">value 3</field>​

忽略从写作

​//not written to document​

映射字段值

​<field name="mapentry[0].key">mapentry[0].value[0]</field>​​​ ​​<field name="mapentry[0].key">mapentry[0].value[1]</field>​​​ ​​<field name="mapentry[1].key">mapentry[1].value[0]</field>​

动态映射字段值

​<field name="'dynamicMappedField_' + mapentry[0].key">mapentry[0].value[0]</field>​​​ ​​<field name="'dynamicMappedField_' + mapentry[0].key">mapentry[0].value[1]</field>​​​ ​​<field name="'dynamicMappedField_' + mapentry[1].key">mapentry[1].value[0]</field>​

位置

​<field name="location">48.362893,14.534437</field>​

您可以通过添加并使用自己的实现初始化自定义转换器来注册自定义转换器,如以下示例所示:​​CustomConversions​​​​SolrTemplate​​​​Converter​

<bean  class="org.springframework.data.solr.core.convert.MappingSolrConverter">
<constructor-arg>
<bean class="org.springframework.data.solr.core.mapping.SimpleSolrMappingContext" />
</constructor-arg>
<property name="customConversions" ref="customConversions" />
</bean>

<bean class="org.springframework.data.solr.core.convert.SolrCustomConversions">
<constructor-arg>
<list>
<bean class="com.acme.MyBeanToSolrInputDocumentConverter" />
</list>
</constructor-arg>
</bean>

<bean class="org.springframework.data.solr.core.SolrTemplate">
<constructor-arg ref="solrClient" />
<property name="solrConverter" ref="solrConverter" />
</bean>

4. 其他溶胶操作支持

本章介绍对无法通过存储库界面直接访问的 Solr 操作(例如分面)的其他支持。建议将这些操作添加为自定义实现,如Spring 数据存储库的自定义实现中所述。

4.1. 集合/核心名称

使用注释,可以通过为其提供 静态值或使用SpEL进行动态评估。​​@SolrDocument​

@SolrDocument(collection = "techproducts")
class StaticCollectionName { ... }

@SolrDocument(collection = "#{@someBean.getCollectionName()}")
class DynamicCollectionName { ... }

批注的类型可通过表达式中的变量获得。​​@SolrDocument​​​​targetType​

4.2. 部分更新

部分更新可以使用哪个实现来完成。​​PartialUpdate​​​​Update​

PartialUpdate update = new PartialUpdate("id", "123");
update.add("name", "updated-name");
solrTemplate.saveBean("collection-1", update);

4.3. 投影

可以通过字段值应用投影。​​@Query​

@Query(fields = { "name", "id" })
List<ProductBean> findByNameStartingWith(String name);

4.4. 刻面

分面不能通过使用直接应用,但支持此功能。以下示例显示了一个分面查询:​​SolrRepository​​​​SolrTemplate​

FacetQuery query = new SimpleFacetQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD))
.setFacetOptions(new FacetOptions().addFacetOnField("name").setFacetLimit(5));
FacetPage<Product> page = solrTemplate.queryForFacetPage("collection-1", query, Product.class);

字段或查询上的分面也可以通过使用来定义。请记住,结果是 a。​​@Facet​​​​FacetPage​

使用允许您定义使用输入参数作为值的占位符。​​@Facet​

以下示例使用注释来定义分面查询:​​@Facet​

@Query(value = "*:*")
@Facet(fields = { "name" }, limit = 5)
FacetPage<Product> findAllFacetOnName(Pageable page);

以下示例显示了另一个带有前缀的分面查询:

@Query(value = "popularity:?0")
@Facet(fields = { "name" }, limit = 5, prefix="?1")
FacetPage<Product> findByPopularityFacetOnName(int popularity, String prefix, Pageable page);

Solr允许在每个字段的基础上定义小平面参数。要向定义的字段添加特殊的分面选项,请使用,如以下示例所示:​​FieldWithFacetParameters​

// produces: f.name.facet.prefix=spring
FacetOptions options = new FacetOptions();
options.addFacetOnField(new FieldWithFacetParameters("name").setPrefix("spring"));

4.4.1. 范围分面

您可以通过在 上配置所需范围来创建范围分面查询。您可以通过创建实例、将选项设置为 a 并查询分面页面来请求范围,如下所示。​​FacetOptions​​​​FacetOptions​​​​FacetQuery​​​​SolrTemplate​

FacetOptions facetOptions = new FacetOptions()
.addFacetByRange(
new FieldWithNumericRangeParameters("price", 5, 20, 5)
.setHardEnd(true)
.setInclude(FacetRangeInclude.ALL)
)
.addFacetByRange(
new FieldWithDateRangeParameters("release", new Date(1420070400), new Date(946684800), "+1YEAR")
.setInclude(FacetRangeInclude.ALL)
.setOther(FacetRangeOther.BEFORE)
);
facetOptions.setFacetMinCount(0);

Criteria criteria = new SimpleStringCriteria("*:*");
SimpleFacetQuery facetQuery = new SimpleFacetQuery(criteria).setFacetOptions(facetOptions);
FacetPage<ExampleSolrBean> statResultPage = solrTemplate.queryForFacetPage("collection-1", facetQuery, ExampleSolrBean.class);

分面范围请求的字段有两种实现:

  • 数值分面范围:用于对数值字段执行范围分面。若要请求范围分面,可以使用类的实例。它的实例化需要字段名称、起始值(数字)、结束值(数字)和间隔(数字);org.springframework.data.solr.core.query.FacetOptions.FieldWithNumericRangeParameters
  • 日期分面范围:用于对日期字段执行范围分面。若要请求范围分面,可以使用类的实例。它的实例化需要字段名称、开始值(日期)、结束值(日期)和间隙(字符串)。您可以使用(例如,表示未来的六个月零三天,四舍五入到最接近的日期)来定义此类字段的间隙。org.springframework.data.solr.core.query.FacetOptions.FieldWithDateRangeParametersorg.apache.solr.util.DateMathParser+6MONTHS+3DAYS/DAY

此外,可以为具有范围参数 () 的字段配置以下属性:​​org.springframework.data.solr.core.query.FacetOptions.FieldWithRangeParameters​

  • 硬端:定义即使结束不满足,是否也应突然结束最后一个范围。setHardEnd(Boolean)(start - end) % gap = 0
  • 包括:定义应如何处理范围分面请求的边界(下限和上限)(独占或包含)。setInclude(org.apache.solr.common.params.FacetParams.FacetRangeInclude)
  • 其他:定义范围分面的附加(其他)计数(例如,范围分面开始之前、范围分面结束之后,甚至开始和结束之间的文档计数)。setOther(org.apache.solr.common.params.FacetParams.FacetRangeOther)

4.4.2. 枢轴刻面

还支持透视分面(决策树),可以使用注释进行查询,如下所示:​​@Facet​

public interface {

@Facet(pivots = @Pivot({ "category", "dimension" }, pivotMinCount = 0))
FacetPage<Product> findByTitle(String title, Pageable page);

@Facet(pivots = @Pivot({ "category", "dimension" }))
FacetPage<Product> findByDescription(String description, Pageable page);

}

或者,可以使用以下命令对其进行查询:​​SolrTemplate​

FacetQuery facetQuery = new SimpleFacetQuery(new SimpleStringCriteria("title:foo"));
FacetOptions facetOptions = new FacetOptions();
facetOptions.setFacetMinCount(0);
facetOptions.addFacetOnPivot("category","dimension");
facetQuery.setFacetOptions(facetOptions);
FacetPage<Product> facetResult = solrTemplate.queryForFacetPage("collection-1", facetQuery, Product.class);

为了检索透视结果,请使用该方法,如下所示:​​getPivot​

List<FacetPivotFieldEntry> pivot = facetResult.getPivot(new SimplePivotField("categories","available"));

4.5. 条款

术语向量不能直接在内部使用,但可以通过应用。请记住,结果是 a。以下示例演示如何创建术语查询:​​SolrRepository​​​​SolrTemplate​​​​TermsPage​

TermsQuery query = SimpleTermsQuery.queryBuilder().fields("name").build();
TermsPage page = solrTemplate.queryForTermsPage("collection-1", query);

4.6. 结果分组和字段折叠

结果分组不能直接在其中使用,但可以通过应用。请记住,结果是 a。以下示例演示如何创建结果组:​​SolrRepository​​​​SolrTemplate​​​​GroupPage​

Field field = new SimpleField("popularity");
Function func = ExistsFunction.exists("description");
Query query = new SimpleQuery("inStock:true");

SimpleQuery groupQuery = new SimpleQuery(new SimpleStringCriteria("*:*"));
GroupOptions groupOptions = new GroupOptions()
.addGroupByField(field)
.addGroupByFunction(func)
.addGroupByQuery(query);
groupQuery.setGroupOptions(groupOptions);

GroupPage<Product> page = solrTemplate.queryForGroupPage("collection-1", query, Product.class);

GroupResult<Product> fieldGroup = page.getGroupResult(field);
GroupResult<Product> funcGroup = page.getGroupResult(func);
GroupResult<Product> queryGroup = page.getGroupResult(query);

4.7. 字段统计

字段统计信息用于从 Solr 检索给定字段的统计信息(,,,,,,,和计算)。您可以提供查询并从返回的内容中读取。例如,您可以使用如下方式执行此操作:​​max​​​​min​​​​sum​​​​count​​​​mean​​​​missing​​​​stddev​​​​distinct​​​​StatsOptions​​​​FieldStatsResult​​​​StatsPage​​​​SolrTemplate​

// simple field stats
StatsOptions statsOptions = new StatsOptions().addField("price");

// query
SimpleQuery statsQuery = new SimpleQuery("*:*");
statsQuery.setStatsOptions(statsOptions);
StatsPage<Product> statsPage = solrTemplate.queryForStatsPage("collection-1", statsQuery, Product.class);

// retrieving stats info
FieldStatsResult priceStatResult = statResultPage.getFieldStatsResult("price");
Object max = priceStatResult.getMax();
Long missing = priceStatResult.getMissing();

您可以通过注释存储库方法来实现相同的结果,如下所示:​​@Stats​

@Query("name:?0")
@Stats(value = { "price" })
StatsPage<Product> findByName(String name, Pageable page);

还支持不同的计算和分面:

// for distinct calculation
StatsOptions statsOptions = new StatsOptions()
.addField("category")
// for distinct calculation
.setCalcDistinct(true)
// for faceting
.addFacet("availability");

// query
SimpleQuery statsQuery = new SimpleQuery("*:*");
statsQuery.setStatsOptions(statsOptions);
StatsPage<Product> statsPage = solrTemplate.queryForStatsPage("collection-1", statsQuery, Product.class);

// field stats
FieldStatsResult categoryStatResult = statResultPage.getFieldStatsResult("category");

// retrieving distinct
List<Object> categoryValues = priceStatResult.getDistinctValues();
Long distinctCount = categoryStatResult.getDistinctCount();

// retrieving faceting
Map<String, StatsResult> availabilityFacetResult = categoryStatResult.getFacetStatsResult("availability");
Long availableCount = availabilityFacetResult.get("true").getCount();

前面示例的注释版本(因此更短)如下:

@Query("name:?0")
@Stats(value = "category", facets = { "availability" }, calcDistinct = true)
StatsPage<Product> findByName(String name);

为了执行选择性分面或选择性非重复计算,可以使用如下方法:​​@SelectiveStats​

// selective distinct faceting
...
Field facetField = getFacetField();
StatsOptions statsOptions = new StatsOptions()
.addField("price")
.addField("category").addSelectiveFacet("name").addSelectiveFacet(facetField);
...
// or annotating repository method as follows
...
@Stats(value = "price", selective = @SelectiveStats(field = "category", facets = { "name", "available" }))
...

// selective distinct calculation
...
StatsOptions statsOptions = new StatsOptions()
.addField("price")
.addField("category").setSelectiveCalcDistinct(true);
...
// or annotating repository method as follows
...
@Stats(value = "price", selective = @SelectiveStats(field = "category", calcDistinct = true))
...

4.8. 过滤器查询

筛选器查询可提高查询速度,并且不会影响文档分数。建议将地理空间搜索实现为筛选器查询。

在 Solr 中,除非另有说明,否则所有距离单位均为千米,点以纬度和经度为单位。

以下示例显示了地理点的筛选器查询(在本例中为奥地利):

Query query = new SimpleQuery(new Criteria("category").is("supercalifragilisticexpialidocious"));
FilterQuery fq = new SimpleFilterQuery(new Criteria("store")
.near(new Point(48.305478, 14.286699), new Distance(5)));
query.addFilterQuery(fq);

还可以使用 定义简单的筛选器查询。​​@Query​

使用允许您定义使用输入参数作为值的占位符。​​@Query​

以下示例显示了一个带有占位符 () 的查询:​:

@Query(value = "*:*", filters = { "inStock:true", "popularity:[* TO 3]" })
List<Product> findAllFilterAvailableTrueAndPopularityLessThanEqual3();

Apache Solr 的 Spring Data (数据)

4.9. 允许的搜索时间

您可以设置允许的搜索完成时间。此值仅适用于搜索,不适用于一般请求。时间以毫秒为单位。小于或等于零的值表示没有时间限制。如果有,可能会返回部分结果。以下示例将搜索时间限制为 100 毫秒:

Query query = new SimpleQuery(new SimpleStringCriteria("field_1:value_1"));
// Allowing maximum of 100ms for this search
query.setTimeAllowed(100);

4.10. 提高文档分数

您可以提高匹配条件的文档分数以影响结果顺序。可以通过设置 boost on或 using for 派生查询来执行此操作。以下示例提升了查询的参数:​​Criteria​​​​@Boost​​​​name​​​​findByNameOrDescription​

Page<Product> findByNameOrDescription(@Boost(2) String name, String description);

4.10.1. 索引时间提升

基于文档和基于字段的索引时间提升都已从Apache Solr 7中删除,因此也已从Apache Solr 4.x的Spring Data中删除。

4.11. 选择请求处理程序

可以直接通过参数选择请求处理程序,也可以通过添加到方法签名来选择请求处理程序。以下示例通过添加以下内容来实现:​​qt​​​​Query​​​​@Query​​​​@Query​

@Query(requestHandler = "/instock")
Page<Product> findByNameOrDescription(String name, String description);

4.12. 使用连接

您可以通过定义 a 的属性在一个 Solr 内核中使用连接。​​Join​​​​Query​

加入在 Solr 4.x 之前不可用。

以下示例演示如何使用联接:

SimpleQuery query = new SimpleQuery(new SimpleStringCriteria("text:ipod"));
query.setJoin(Join.from("manu_id_s").to("id"));

4.13. 突出显示

要在搜索结果中突出显示匹配项,您可以添加。提供没有任何其他属性将突出显示 a 中的所有字段。​​HighlightOptions​​​​SimpleHighlightQuery​​​​HighlightOptions​​​​SolrDocument​

您可以通过添加来设置特定于字段的突出显示参数。​​FieldWithHighlightParameters​​​​HighlightOptions​

下面的示例为查询中的所有字段设置突出显示:

SimpleHighlightQuery query = new SimpleHighlightQuery(new SimpleStringCriteria("name:with"));
query.setHighlightOptions(new HighlightOptions());
HighlightPage<Product> page = solrTemplate.queryForHighlightPage("collection-1", query, Product.class);

并非所有参数都可以通过二传手和吸气器获得,但可以直接添加。

以下示例在两个字段上设置突出显示:

SimpleHighlightQuery query = new SimpleHighlightQuery(new SimpleStringCriteria("name:with"));
query.setHighlightOptions(new HighlightOptions().addHighlightParameter("hl.bs.country", "at"));

若要将突出显示应用于派生查询,可以使用。如果定义了 noare,则突出显示将应用于所有字段。​​@Highlight​​​​fields​

@Highlight(prefix = "<b>", postfix = "</b>")
HighlightPage<Product> findByName(String name, Pageable page);

4.14. 拼写检查

拼写检查根据实际查询提供搜索词建议。有关更多详细信息,请参阅Solr 参考。

4.14.1. 拼写检查选项

设置拼写检查查询参数时将添加到请求中,如以下示例所示:​​SpellcheckOptions​

SimpleQuery q = new SimpleQuery("name:gren");
q.setSpellcheckOptions(SpellcheckOptions.spellcheck()
.dictionaries("dict1", "dict2")
.count(5)
.extendedResults());
q.setRequestHandler("/spell");

SpellcheckedPage<Product> found = template.query(q, Product.class);

通过设置启用拼写检查。设置请求参数。​​SpellcheckOptions​​​​spellcheck=on​

设置用于查找的词典。

设置要返回的最大建议数。

启用扩展结果,包括学期频率等。

设置请求处理程序,该处理程序必须能够处理建议。

运行查询。

4.14.2. ​​@Spellcheck​

注释允许在级别上使用拼写检查功能。以下示例演示如何使用它:​​@Spellcheck​​​​Repository​

public interface ProductRepository extends Repository<Product, String> {

@Query(requestHandler = "/spell")
@Spellcheck(dictionaries = { "dict1", "dic2" }, count=5, extendedResults = true)
SpellcheckedPage<Product> findByName(String name, Pageable page);

}

4.15. 使用函数

Solr支持查询中的多个函数表达式,并包含许多函数。您可以通过实现来添加自定义函数。下表列出了支持的功能:​​Function​

Table 3. Functions


溶胶功能

​CurrencyFunction​

​currency(field_name,[CODE])​

​DefaultValueFunction​

​def(field|function,defaultValue)​

​DistanceFunction​

​dist(power, pointA, pointB)​

​DivideFunction​

​div(x,y)​

​ExistsFunction​

​exists(field|function)​

​GeoDistanceFunction​

​geodist(sfield, latitude, longitude)​

​GeoHashFunction​

​geohash(latitude, longitude)​

​IfFunction​

​if(value|field|function,trueValue,falseValue)​

​MaxFunction​

​max(field|function,value)​

​NotFunction​

​not(field|function)​

​ProductFunction​

​product(x,y,…)​

​QueryFunction​

​query(x)​

​TermFrequencyFunction​

​termfreq(field,term)​

以下示例使用:​​QueryFunction​

SimpleQuery query = new SimpleQuery(new SimpleStringCriteria("text:ipod"));
query.addFilterQuery(new FilterQuery(Criteria.where(QueryFunction.query("name:sol*"))));

4.16. 实时获取

实时获取允许使用唯一键检索任何文档的最新版本,而无需重新打开搜索器。

实时获取依赖于更新日志功能。

以下示例显示了实时获取:

例 64。实时获取

Optional<Product> product = solrTemplate.getById("collection-1", "123", Product.class);

您可以通过提供集合来检索多个文档,如下所示:​​ids​

例 65。实时多获取

Collection<String> ids = Arrays.asList("123", "134");
Collection<Product> products = solrTemplate.getByIds("collection-1", ids, Product.class);

4.17. 特殊字段

Solr 包括许多特殊字段,包括分数字段。

4.17.1. ​​@Score​

为了加载查询结果的分数信息,您可以添加一个用注释批注的字段,指示该属性包含文档的分数。​​@Score​

score 属性必须是数字,并且每个文档只能出现一次。

以下示例显示具有分数字段的文档:

public class MyEntity {

@Id
private String id;

@Score
private Float score;

// setters and getters ...

}

4.18. 嵌套文档

嵌套文档允许父子关系中的其他文档内的文档。

嵌套文档需要与父文档一起编制索引,并且不能单独更新。但是,嵌套文档在索引中显示为单个文档。 解析父子关系在查询时完成。

若要指示应将属性视为嵌套对象,必须使用 bothor 对其进行批注。 下面使用注释:​​@o.a.s.c.solrj.beans.Field(child=true)​​​​@o.s.d.s.core.mapping.ChildDocument​​​​@ChildDocument​

public class Book {

@Id String id;
@Indexed("type_s") String type;
@Indexed("title_t") String title;
@Indexed("author_s") String author;
@Indexed("publisher_s") String publisher;

@ChildDocument List<Review> reviews;

// setters and getters ...

}

public class Review {

@Id String id;
@Indexed("type_s") String type;
@Indexed("review_dt") Date date;
@Indexed("stars_i") int stars;
@Indexed("author_s") String author;
@Indexed("comment_t") String comment;

}

多个子文档可以与父文档关联,也可以使用域类型来存储单个关系。

请注意,嵌套文档还需要具有唯一分配。​​id​

假设 assumingis, andresolves to, 检索其子关系可以通过更改 thequery 参数来完成,如以下示例所示:​​Book#type​​​​book​​​​Review#type​​​​review​​​​Book​​​​reviews​​​​fl​

Query query = new SimpleQuery(where("id").is("theWayOfKings"));
query.addProjectionOnField(new SimpleField("*"));
query.addProjectionOnField(new SimpleField("[child parentFilter=type_s:book]"));

return solrTemplate.queryForObject("books", query, Book.class);

父筛选器始终定义索引中完整的父文档集,而不是单个文档的父文档集。

附录

附录 A:命名空间引用

元素​​<repositories />​

该元素触发了 Spring 数据存储库基础结构的设置。最重要的属性是,它定义了要扫描 Spring 数据存储库接口的包。请参阅“XML 配置”。下表描述了元素的属性:​​<repositories />​​​​base-package​​​​<repositories />​

Table 4. Attributes

名字

描述

​base-package​

定义要扫描的包,以查找在自动检测模式下扩展的存储库接口(实际接口由特定的 Spring 数据模块确定)。还会扫描已配置软件包下的所有软件包。允许使用通配符。​​*Repository​

​repository-impl-postfix​

定义用于自动检测自定义存储库实现的后缀。名称以配置的后缀结尾的类被视为候选类。默认为。​​Impl​

​query-lookup-strategy​

确定用于创建查找器查询的策略。有关详细信息,请参阅“查询查找策略​”。默认为。​​create-if-not-found​

​named-queries-location​

定义搜索包含外部定义查询的属性文件的位置。

​consider-nested-repositories​

是否应考虑嵌套存储库接口定义。默认为。​​false​

附录 B:填充器命名空间参考

<填充器 /> 元素

该元素允许通过 Spring 数据存储库基础架构填充数据存储。[​​1​​]​<populator />​

Table 5. Attributes

名字

描述

​locations​

应填充从存储库中查找要读取对象的文件的位置。

附录 C:存储库查询关键字

支持的查询方法主题关键字

下表列出了 Spring 数据存储库查询派生机制通常支持的主题关键字,以表达谓词。 有关支持的关键字的确切列表,请参阅特定于商店的文档,因为此处列出的某些关键字可能在特定商店中不受支持。

Table 6. Query subject keywords

关键词

描述

​find…By​​​, , , , , ​​read…By​​​​get…By​​​​query…By​​​​search…By​​​​stream…By​

常规查询方法通常返回存储库类型、主子类型或结果包装器(如),或任何其他特定于存储的结果包装器。可以用作,或与其他关键字结合使用。​​Collection​​​​Streamable​​​​Page​​​​GeoResults​​​​findBy…​​​​findMyDomainTypeBy…​

​exists…By​

存在投影,返回通常为结果。​​boolean​

​count…By​

计数投影返回数值结果。

​delete…By​​​, ​​remove…By​

删除查询方法不返回任何结果 () 或删除计数。​​void​

​…First<number>…​​​, ​​…Top<number>…​

将查询结果限制为第一个结果。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​<number>​​​​find​​​​by​

​…Distinct…​

使用非重复查询仅返回唯一结果。请参阅特定于商店的文档是否支持该功能。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​find​​​​by​

支持的查询方法谓词关键字和修饰符

下表列出了 Spring 数据存储库查询派生机制通常支持的谓词关键字。 但是,请参阅特定于商店的文档,了解支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。

Table 7. Query predicate keywords

逻辑关键字

关键字表达式

​AND​

​And​

​OR​

​Or​

​AFTER​

​After​​​, ​​IsAfter​

​BEFORE​

​Before​​​, ​​IsBefore​

​CONTAINING​

​Containing​​​, , ​​IsContaining​​​​Contains​

​BETWEEN​

​Between​​​, ​​IsBetween​

​ENDING_WITH​

​EndingWith​​​, , ​​IsEndingWith​​​​EndsWith​

​EXISTS​

​Exists​

​FALSE​

​False​​​, ​​IsFalse​

​GREATER_THAN​

​GreaterThan​​​, ​​IsGreaterThan​

​GREATER_THAN_EQUALS​

​GreaterThanEqual​​​, ​​IsGreaterThanEqual​

​IN​

​In​​​, ​​IsIn​

​IS​

​Is​​​,,(或无关键字)​​Equals​

​IS_EMPTY​

​IsEmpty​​​, ​​Empty​

​IS_NOT_EMPTY​

​IsNotEmpty​​​, ​​NotEmpty​

​IS_NOT_NULL​

​NotNull​​​, ​​IsNotNull​

​IS_NULL​

​Null​​​, ​​IsNull​

​LESS_THAN​

​LessThan​​​, ​​IsLessThan​

​LESS_THAN_EQUAL​

​LessThanEqual​​​, ​​IsLessThanEqual​

​LIKE​

​Like​​​, ​​IsLike​

​NEAR​

​Near​​​, ​​IsNear​

​NOT​

​Not​​​, ​​IsNot​

​NOT_IN​

​NotIn​​​, ​​IsNotIn​

​NOT_LIKE​

​NotLike​​​, ​​IsNotLike​

​REGEX​

​Regex​​​, , ​​MatchesRegex​​​​Matches​

​STARTING_WITH​

​StartingWith​​​, , ​​IsStartingWith​​​​StartsWith​

​TRUE​

​True​​​, ​​IsTrue​

​WITHIN​

​Within​​​, ​​IsWithin​

除了筛选器谓词之外,还支持以下修饰符列表:

Table 8. Query predicate modifier keywords

关键词

描述

​IgnoreCase​​​, ​​IgnoringCase​

与谓词关键字一起使用,用于不区分大小写的比较。

​AllIgnoreCase​​​, ​​AllIgnoringCase​

忽略所有合适属性的大小写。在查询方法谓词中的某处使用。

​OrderBy…​

指定静态排序顺序,后跟属性路径和方向(例如)。​​OrderByFirstnameAscLastnameDesc​

附录 D:存储库查询返回类型

支持的查询返回类型

下表列出了 Spring 数据存储库通常支持的返回类型。 但是,请参阅特定于商店的文档以获取支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。

地理空间类型(如、和)仅适用于支持地理空间查询的数据存储。 某些存储模块可能会定义自己的结果包装器类型。​​GeoResult​​​​GeoResults​​​​GeoPage​

Table 9. Query return types

返回类型

描述

​void​

表示无返回值。

Java 原语。

包装器类型

Java 包装器类型。

​T​

一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​null​​​​IncorrectResultSizeDataAccessException​

​Iterator<T>​

一。​​Iterator​

​Collection<T>​

一个。​​Collection​

​List<T>​

一个。​​List​

​Optional<T>​

爪哇8或番石榴。期望查询方法最多返回一个结果。如果未找到结果,则返回 oris 。多个结果触发 an.​​Optional​​​​Optional.empty()​​​​Optional.absent()​​​​IncorrectResultSizeDataAccessException​

​Option<T>​

要么是斯卡拉,要么是vavrtype。在语义上与前面描述的Java 8的行为相同。​​Option​​​​Optional​

​Stream<T>​

A Java 8 .​​Stream​

​Streamable<T>​

该直接的便利扩展公开了流式传输,映射和过滤结果,连接它们等的方法。​​Iterable​

实现和采用构造函数或工厂方法参数的类型​​Streamable​​​​Streamable​

公开采用 aas 参数的构造函数或/工厂方法的类型。有关详细信息,请参阅返回自定义可流式传输包装器类型​。​​….of(…)​​​​….valueOf(…)​​​​Streamable​

瓦夫尔,,,​​Seq​​​​List​​​​Map​​​​Set​

Vavr 集合类型。有关详细信息,请参阅​​对 Vavr 集合的支持​​。

​Future<T>​

A. 期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​Future​​​​@Async​

​CompletableFuture<T>​

A Java 8.期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​CompletableFuture​​​​@Async​

​ListenableFuture​

A. 期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​org.springframework.util.concurrent.ListenableFuture​​​​@Async​

​Slice<T>​

一个大小的数据块,指示是否有更多可用数据。需要方法参数。​​Pageable​

​Page<T>​

A 包含其他信息,例如结果总数。需要方法参数。​​Slice​​​​Pageable​

​GeoResult<T>​

包含附加信息(如到参考位置的距离)的结果条目。

​GeoResults<T>​

包含附加信息的列表,例如到参考位置的平均距离。​​GeoResult<T>​

​GeoPage<T>​

Awith,例如到参考位置的平均距离。​​Page​​​​GeoResult<T>​

​Mono<T>​

使用反应式存储库发射零个或一个元素的项目反应器。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Mono​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flux<T>​

使用反应式存储库发射零个、一个或多个元素的项目反应器。返回的查询还可以发出无限数量的元素。​​Flux​​​​Flux​

​Single<T>​

一个 RxJava使用反应式存储库发出单个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Single​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Maybe<T>​

使用反应式存储库的 RxJavaemitting 零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Maybe​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flowable<T>​

使用反应式存储库的 RxJavaemitting 零个、一个或多个元素。返回的查询还可以发出无限数量的元素。​​Flowable​​​​Flowable​