此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Elasticsearch 5.4.0spring-doc.cn

脚本化字段和运行时字段

Spring Data Elasticsearch 支持脚本化字段和运行时字段。 有关此内容的详细信息,请参阅有关脚本 (www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html) 和运行时字段 (www.elastic.co/guide/en/elasticsearch/reference/8.9/runtime.html) 的 Elasticsearch 文档。 在 Spring Data Elasticsearch 的上下文中,您可以使用spring-doc.cn

  • 脚本化字段,用于返回在结果文档上计算并添加到返回文档的字段。spring-doc.cn

  • 运行时字段,这些字段是根据存储的文档计算的,可以在查询中使用和/或在搜索结果中返回。spring-doc.cn

以下代码片段将展示您可以执行的操作(这些代码片段显示了命令式代码,但反应式实现的工作方式类似)。spring-doc.cn

person 实体

这些示例中使用的 enity 是一个实体。 此实体具有 a 和 an 属性。 虽然 birthdate 是固定的,但 age 取决于发出查询的时间,并且需要动态计算。PersonbirthDateagespring-doc.cn

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.lang.Nullable;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import static org.springframework.data.elasticsearch.annotations.FieldType.*;

import java.lang.Integer;

@Document(indexName = "persons")
public record Person(
        @Id
        @Nullable
        String id,
        @Field(type = Text)
        String lastName,
        @Field(type = Text)
        String firstName,
        @Field(type = Keyword)
        String gender,
        @Field(type = Date, format = DateFormat.basic_date)
        LocalDate birthDate,
        @Nullable
        @ScriptedField Integer age                   (1)
) {
    public Person(String id,String lastName, String firstName, String gender, String birthDate) {
        this(id,                                     (2)
            lastName,
            firstName,
            LocalDate.parse(birthDate, DateTimeFormatter.ISO_LOCAL_DATE),
            gender,
            null);
    }
}
1 该属性将被计算并填充到搜索结果中。age
2 一个方便的构造函数来设置测试数据。

请注意,该属性带有 . 这会禁止在索引映射中写入相应的条目,并将属性标记为目标,以从搜索响应中放置计算字段。age@ScriptedFieldspring-doc.cn

存储库界面

此示例中使用的存储库:spring-doc.cn

public interface PersonRepository extends ElasticsearchRepository<Person, String> {

    SearchHits<Person> findAllBy(ScriptedField scriptedField);

    SearchHits<Person> findByGenderAndAgeLessThanEqual(String gender, Integer age, RuntimeField runtimeField);
}

服务类

服务类注入了一个存储库和一个实例,用于显示填充和使用该属性的几种方法。 我们将代码分成不同的部分来放置解释ElasticsearchOperationsagespring-doc.cn

import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.RuntimeField;
import org.springframework.data.elasticsearch.core.query.ScriptData;
import org.springframework.data.elasticsearch.core.query.ScriptType;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PersonService {
    private final ElasticsearchOperations operations;
    private final PersonRepository repository;

    public PersonService(ElasticsearchOperations operations, SaRPersonRepository repository) {
        this.operations = operations;
        this.repository = repository;
    }

    public void save() { (1)
        List<Person> persons = List.of(
                new Person("1", "Smith", "Mary", "f", "1987-05-03"),
                new Person("2", "Smith", "Joshua", "m", "1982-11-17"),
                new Person("3", "Smith", "Joanna", "f", "2018-03-27"),
                new Person("4", "Smith", "Alex", "m", "2020-08-01"),
                new Person("5", "McNeill", "Fiona", "f", "1989-04-07"),
                new Person("6", "McNeill", "Michael", "m", "1984-10-20"),
                new Person("7", "McNeill", "Geraldine", "f", "2020-03-02"),
                new Person("8", "McNeill", "Patrick", "m", "2022-07-04"));

        repository.saveAll(persons);
    }
1 一种在 Elasticsearch 中存储一些数据的实用方法。

脚本化字段

下一篇文章将介绍如何使用脚本化字段来计算和返回人员的年龄。 脚本化字段只能向返回的数据中添加一些内容,年龄不能在查询中使用(请参阅运行时字段)。spring-doc.cn

    public SearchHits<Person> findAllWithAge() {

        var scriptedField = ScriptedField.of("age",                               (1)
                ScriptData.of(b -> b
                        .withType(ScriptType.INLINE)
                        .withScript("""
                                Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
                                Instant startDate = doc['birth-date'].value.toInstant();
                                return (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
                                """)));

        // version 1: use a direct query
        var query = new StringQuery("""
                { "match_all": {} }
                """);
        query.addScriptedField(scriptedField);                                    (2)
        query.addSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*")));    (3)

        var result1 = operations.search(query, Person.class);                     (4)

        // version 2: use the repository
        var result2 = repository.findAllBy(scriptedField);                        (5)

        return result1;
    }
1 定义 计算一个人的年龄。ScriptedField
2 使用 时,将脚本化字段添加到查询中。Query
3 将脚本化字段添加到 时,还需要一个额外的源过滤器,以便从文档源中检索普通字段。Query
4 获取实体现在在其属性中设置了值的数据。Personage
5 使用存储库时,只需将脚本字段添加为 method 参数即可。

运行时字段

使用运行时字段时,计算值可用于查询本身。 在下面的代码中,这用于对给定的性别和最大年龄的 persons 运行查询:spring-doc.cn

    public SearchHits<Person> findWithGenderAndMaxAge(String gender, Integer maxAge) {

        var runtimeField = new RuntimeField("age", "long", """                    (1)
                                Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
                                Instant startDate = doc['birthDate'].value.toInstant();
                                emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
                """);

        // variant 1 : use a direct query
        var query = CriteriaQuery.builder(Criteria
                        .where("gender").is(gender)
                        .and("age").lessThanEqual(maxAge))
                .withRuntimeFields(List.of(runtimeField))                         (2)
                .withFields("age")                                                (3)
                .withSourceFilter(FetchSourceFilter.of(b -> b.withIncludes("*"))) (4)
                .build();

        var result1 = operations.search(query, Person.class);                     (5)

        // variant 2: use the repository                                          (6)
        var result2 = repository.findByGenderAndAgeLessThanEqual(gender, maxAge, runtimeField);

        return result1;
    }
}
1 定义计算人员年龄的 runtime 字段。请参阅 asciidoctor.org/docs/user-manual/#builtin-attributes for builtin attributes.
2 使用 时,添加 runtime 字段。Query
3 将脚本化字段添加到 时,需要一个额外的字段参数才能返回计算值。Query
4 将脚本化字段添加到 时,还需要一个额外的源过滤器,以便从文档源中检索普通字段。Query
5 获取使用查询筛选的数据,以及返回的 entite 设置了 age 属性的位置。
6 使用存储库时,只需将 runtime 字段添加为 method 参数即可。

除了在查询上定义运行时字段外,还可以通过将注释的属性设置为指向包含运行时字段定义的 JSON 文件,在索引中定义这些字段。runtimeFieldsPath@Mappingspring-doc.cn