本章介绍了对无法通过存储库接口直接访问的 Elasticsearch 操作的其他支持。 建议将这些操作添加为自定义实现,如 自定义存储库实现 中所述。
索引设置
使用 Spring Data Elasticsearch 创建 Elasticsearch 索引时,可以使用注释定义不同的索引设置。
可以使用以下参数:@Setting
- 
useServerConfiguration不发送任何设置参数,因此 Elasticsearch 服务器配置决定了它们。 - 
settingPath指一个 JSON 文件,用于定义类路径中必须可解析的设置 - 
shards要使用的分片数,默认为 1 - 
replicas副本数,默认为 1 - 
refreshIntervall,默认为“1s” - 
indexStoreType,默认为“fs” 
也可以定义索引排序(查看链接的 Elasticsearch 文档,了解可能的字段类型和值):
@Document(indexName = "entities")
@Setting(
  sortFields = { "secondField", "firstField" },                                  (1)
  sortModes = { Setting.SortMode.max, Setting.SortMode.min },                    (2)
  sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc },
  sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first })
class Entity {
    @Nullable
    @Id private String id;
    @Nullable
    @Field(name = "first_field", type = FieldType.Keyword)
    private String firstField;
    @Nullable @Field(name = "second_field", type = FieldType.Keyword)
    private String secondField;
    // getter and setter...
}
| 1 | 定义排序字段时,请使用 Java 属性的名称 (firstField),而不是可能为 Elasticsearch 定义的名称 (first_field) | 
| 2 | sortModes,并且是可选的,但如果设置了它们,则条目数必须与元素数匹配sortOrderssortMissingValuessortFields | 
| 1 | 定义排序字段时,请使用 Java 属性的名称 (firstField),而不是可能为 Elasticsearch 定义的名称 (first_field) | 
| 2 | sortModes,并且是可选的,但如果设置了它们,则条目数必须与元素数匹配sortOrderssortMissingValuessortFields | 
索引映射
当Spring Data Elasticsearch使用这些方法创建索引映射时,它会使用映射注释概述中描述的注释,尤其是注释。
除此之外,还可以将注释添加到类中。
此批注具有以下属性:IndexOperations.createMapping()@Field@Mapping
- 
mappingPathJSON 格式的类路径资源;如果它不是空的,则将其用作映射,不执行其他映射处理。 - 
enabled当设置为 false 时,此标志将写入映射,并且不执行进一步的处理。 - 
dateDetection并在未设置为 时在映射中设置相应的属性。numericDetectionDEFAULT - 
dynamicDateFormats当此 String 数组不为空时,它定义用于自动日期检测的日期格式。 - 
runtimeFieldsPathJSON 格式的类路径资源,其中包含写入索引映射的运行时字段的定义,例如: 
{
  "day_of_week": {
    "type": "keyword",
    "script": {
      "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
    }
  }
}
过滤器生成器
筛选器生成器提高了查询速度。
private ElasticsearchOperations operations;
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query query = NativeQuery.builder()
	.withQuery(q -> q
		.matchAll(ma -> ma))
	.withFilter( q -> q
		.bool(b -> b
			.must(m -> m
				.term(t -> t
					.field("id")
					.value(documentId))
			)))
	.build();
SearchHits<SampleEntity> sampleEntities = operations.search(query, SampleEntity.class, index);
使用滚动显示大结果集
Elasticsearch 有一个滚动 API,用于获取块中的大结果集。
Spring Data Elasticsearch 在内部使用它来提供该方法的实现。<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query searchQuery = NativeQuery.builder()
    .withQuery(q -> q
        .matchAll(ma -> ma))
    .withFields("message")
    .withPageable(PageRequest.of(0, 10))
    .build();
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
index);
List<SampleEntity> sampleEntities = new ArrayList<>();
while (stream.hasNext()) {
  sampleEntities.add(stream.next());
}
stream.close();
API 中没有访问滚动 ID 的方法,如果有必要访问它,可以使用以下方法(这是不同实现的基本实现):SearchOperationsAbstractElasticsearchTemplateElasticsearchOperations
@Autowired ElasticsearchOperations operations;
AbstractElasticsearchTemplate template = (AbstractElasticsearchTemplate)operations;
IndexCoordinates index = IndexCoordinates.of("sample-index");
Query query = NativeQuery.builder()
    .withQuery(q -> q
        .matchAll(ma -> ma))
    .withFields("message")
    .withPageable(PageRequest.of(0, 10))
    .build();
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, query, SampleEntity.class, index);
String scrollId = scroll.getScrollId();
List<SampleEntity> sampleEntities = new ArrayList<>();
while (scroll.hasSearchHits()) {
  sampleEntities.addAll(scroll.getSearchHits());
  scrollId = scroll.getScrollId();
  scroll = template.searchScrollContinue(scrollId, 1000, SampleEntity.class);
}
template.searchScrollClear(scrollId);
要将 Scroll API 与存储库方法结合使用,必须将返回类型定义为 Elasticsearch 存储库中的内容。
然后,该方法的实现将使用 ElasticsearchTemplate 中的 scroll 方法。Stream
interface SampleEntityRepository extends Repository<SampleEntity, String> {
    Stream<SampleEntity> findBy();
}
排序选项
除了分页和排序中描述的默认排序选项外,Spring Data Elasticsearch 还提供了派生自 的类。
它提供了额外的参数,在指定结果排序时可以发送到 Elasticsearch(参见 www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html)。org.springframework.data.elasticsearch.core.query.Orderorg.springframework.data.domain.Sort.Order
还有一个类可用于按地理距离排序搜索操作的结果。org.springframework.data.elasticsearch.core.query.GeoDistanceOrder
如果要检索的类具有名为 location 的属性,则以下命令将按到给定点的距离对结果进行排序:GeoPointSort
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
运行时字段
从 Elasticsearch 的 7.12 版本开始,添加了运行时字段 (www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html) 的功能。 Spring Data Elasticsearch 通过两种方式支持这一点:
索引映射中的运行时字段定义
定义运行时字段的第一种方法是将定义添加到索引映射中(请参阅 www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html)。 要在 Spring Data Elasticsearch 中使用此方法,用户必须提供包含相应定义的 JSON 文件,例如:
{
  "day_of_week": {
    "type": "keyword",
    "script": {
      "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
    }
  }
}
然后,必须在实体的注释中设置此 JSON 文件的路径(该路径必须存在于类路径上):@Mapping
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/runtime-fields.json")
public class RuntimeFieldEntity {
	// properties, getter, setter,...
}
在查询上设置的运行时字段定义
定义运行时字段的第二种方法是将定义添加到搜索查询中(请参阅 www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html)。 下面的代码示例显示了如何使用 Spring Data Elasticsearch 执行此操作:
使用的实体是具有属性的简单对象:price
@Document(indexName = "some_index_name")
public class SomethingToBuy {
	private @Id @Nullable String id;
	@Nullable @Field(type = FieldType.Text) private String description;
	@Nullable @Field(type = FieldType.Double) private Double price;
	// getter and setter
}
以下查询使用一个运行时字段,该字段通过将价格添加 19% 来计算值,并在搜索查询中使用此值来查找高于或等于给定值的所有实体:priceWithTaxpriceWithTax
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
query.addRuntimeField(runtimeField);
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
这适用于接口的每个实现。Query
时间点 (PIT) API
ElasticsearchOperations支持 Elasticsearch 的时间点 API(参见 www.elastic.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html)。
以下代码片段演示如何将此功能用于虚构类:Person
ElasticsearchOperations operations; // autowired
Duration tenSeconds = Duration.ofSeconds(10);
String pit = operations.openPointInTime(IndexCoordinates.of("person"), tenSeconds); (1)
// create query for the pit
Query query1 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Smith"))
    .withPointInTime(new Query.PointInTime(pit, tenSeconds))                        (2)
    .build();
SearchHits<Person> searchHits1 = operations.search(query1, Person.class);
// do something with the data
// create 2nd query for the pit, use the id returned in the previous result
Query query2 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Miller"))
    .withPointInTime(
        new Query.PointInTime(searchHits1.getPointInTimeId(), tenSeconds))          (3)
    .build();
SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
// do something with the data
operations.closePointInTime(searchHits2.getPointInTimeId());                        (4)
| 1 | 为索引(可以是多个名称)和保持活动持续时间创建时间点,并检索其 ID | 
| 2 | 将该 ID 与下一个 keep-alive 值一起传递到查询中以进行搜索 | 
| 3 | 对于下一个查询,请使用从上次搜索返回的 ID | 
| 4 | 完成后,使用上次返回的 ID 关闭时间点 | 
| 1 | 为索引(可以是多个名称)和保持活动持续时间创建时间点,并检索其 ID | 
| 2 | 将该 ID 与下一个 keep-alive 值一起传递到查询中以进行搜索 | 
| 3 | 对于下一个查询,请使用从上次搜索返回的 ID | 
| 4 | 完成后,使用上次返回的 ID 关闭时间点 | 
搜索模板支持
支持使用搜索模板 API。
要使用它,首先需要创建一个存储的脚本。
该接口扩展,提供必要的功能。
此处使用的示例假定我们有一个属性名为 的实体。
搜索模板脚本可以像这样保存:ElasticsearchOperationsScriptOperationsPersonfirstName
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.script.Script;
operations.putScript(                            (1)
  Script.builder()
    .withId("person-firstname")                  (2)
    .withLanguage("mustache")                    (3)
    .withSource("""                              (4)
      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "firstName": "{{firstName}}"   (5)
                }
              }
            ]
          }
        },
        "from": "{{from}}",                      (6)
        "size": "{{size}}"                       (7)
      }
      """)
    .build()
);
| 1 | 使用该方法存储搜索模板脚本putScript() | 
| 2 | 脚本的名称/ID | 
| 3 | 搜索模板中使用的脚本必须使用小胡子语言。 | 
| 4 | 脚本源 | 
| 5 | 脚本中的搜索参数 | 
| 6 | 寻呼请求偏移量 | 
| 7 | 寻呼请求大小 | 
为了在搜索查询中使用搜索模板,Spring Data Elasticsearch 提供了 ,这是该接口的实现。SearchTemplateQueryorg.springframework.data.elasticsearch.core.query.Query
在下面的代码中,我们将使用搜索模板查询将调用添加到自定义存储库实现(请参阅自定义存储库实现)中,作为如何将其集成到存储库调用中的示例。
我们首先定义自定义仓库片段接口:
interface PersonCustomRepository {
	SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable);
}
此存储库片段的实现如下所示:
public class PersonCustomRepositoryImpl implements PersonCustomRepository {
  private final ElasticsearchOperations operations;
  public PersonCustomRepositoryImpl(ElasticsearchOperations operations) {
    this.operations = operations;
  }
  @Override
  public SearchPage<Person> findByFirstNameWithSearchTemplate(String firstName, Pageable pageable) {
    var query = SearchTemplateQuery.builder()                               (1)
      .withId("person-firstname")                                           (2)
      .withParams(
        Map.of(                                                             (3)
          "firstName", firstName,
          "from", pageable.getOffset(),
          "size", pageable.getPageSize()
          )
      )
      .build();
    SearchHits<Person> searchHits = operations.search(query, Person.class); (4)
    return SearchHitSupport.searchPageFor(searchHits, pageable);
  }
}
| 1 | 创建一个SearchTemplateQuery | 
| 2 | 提供搜索模板的 ID | 
| 3 | 参数在Map<String,Object> | 
| 4 | 以与其他查询类型相同的方式进行搜索。 | 
| 1 | 使用该方法存储搜索模板脚本putScript() | 
| 2 | 脚本的名称/ID | 
| 3 | 搜索模板中使用的脚本必须使用小胡子语言。 | 
| 4 | 脚本源 | 
| 5 | 脚本中的搜索参数 | 
| 6 | 寻呼请求偏移量 | 
| 7 | 寻呼请求大小 | 
| 1 | 创建一个SearchTemplateQuery | 
| 2 | 提供搜索模板的 ID | 
| 3 | 参数在Map<String,Object> | 
| 4 | 以与其他查询类型相同的方式进行搜索。 | 
嵌套排序
Spring Data Elasticsearch 支持在嵌套对象 (www.elastic.co/guide/en/elasticsearch/reference/8.9/sort-search-results.html#nested-sorting)
下面的示例取自该类,演示如何定义嵌套排序。org.springframework.data.elasticsearch.core.query.sort.NestedSortIntegrationTests
var filter = StringQuery.builder("""
	{ "term": {"movies.actors.sex": "m"} }
	""").build();
var order = new org.springframework.data.elasticsearch.core.query.Order(Sort.Direction.DESC,
	"movies.actors.yearOfBirth")
	.withNested(
		Nested.builder("movies")
			.withNested(
				Nested.builder("movies.actors")
					.withFilter(filter)
					.build())
			.build());
var query = Query.findAll().addSort(Sort.by(order));
关于过滤器查询:不能在此处使用,因为此查询将转换为在过滤器上下文中不起作用的 Elasticsearch 嵌套查询。所以只能在这里使用或可以使用。使用其中之一时,如上面的术语查询,必须使用 Elasticsearch 字段名称,因此在使用定义重新定义这些字段名称时要小心。CriteriaQueryStringQueryNativeQuery@Field(name="…")
对于订单路径和嵌套路径的定义,应使用 Java 实体属性名称。