Join-Type implementation

Spring Data Elasticsearch 支持 Join data type以创建相应的索引映射并存储相关信息。

Spring Data Elasticsearch supports the Join data type for creating the corresponding index mappings and for storing the relevant information.

Setting up the data

实体用于父子连接关系中,它必须具有类型为 JoinField 的属性,该属性必须带有注释。我们假设一个 Statement 实体,其中语句可以是 问题答案注释投票(本例中还展示了一个 生成器,它不是必需的,但稍后在示例代码中使用):

For an entity to be used in a parent child join relationship, it must have a property of type JoinField which must be annotated. Let’s assume a Statement entity where a statement may be a question, an answer, a comment or a vote (a Builder is also shown in this example, it’s not necessary, but later used in the sample code):

@Document(indexName = "statements")
@Routing("routing")                                                                       1
public class Statement {
    @Id
    private String id;

    @Field(type = FieldType.Text)
    private String text;

    @Field(type = FieldType.Keyword)
    private String routing;

    @JoinTypeRelations(
        relations =
            {
                @JoinTypeRelation(parent = "question", children = {"answer", "comment"}), 2
                @JoinTypeRelation(parent = "answer", children = "vote")                   3
            }
    )
    private JoinField<String> relation;                                                   4

    private Statement() {
    }

    public static StatementBuilder builder() {
        return new StatementBuilder();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getRouting() {
        return routing;
    }

    public void setRouting(Routing routing) {
        this.routing = routing;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public JoinField<String> getRelation() {
        return relation;
    }

    public void setRelation(JoinField<String> relation) {
        this.relation = relation;
    }

    public static final class StatementBuilder {
        private String id;
        private String text;
        private String routing;
        private JoinField<String> relation;

        private StatementBuilder() {
        }

        public StatementBuilder withId(String id) {
            this.id = id;
            return this;
        }

        public StatementBuilder withRouting(String routing) {
            this.routing = routing;
            return this;
        }

        public StatementBuilder withText(String text) {
            this.text = text;
            return this;
        }

        public StatementBuilder withRelation(JoinField<String> relation) {
            this.relation = relation;
            return this;
        }

        public Statement build() {
            Statement statement = new Statement();
            statement.setId(id);
            statement.setRouting(routing);
            statement.setText(text);
            statement.setRelation(relation);
            return statement;
        }
    }
}
1 有关路由相关的信息,请参阅 Routing values
2 for routing related info see Routing values
3 一个问题可以有答案和评论
4 a question can have answers and comments
5 一个答案可以有投票
6 an answer can have votes
7 JoinField 属性用于把关系的名称 (question, answer, commentvote) 和父 ID 结合在一起。普通类型必须和 @Id 注解的属性一样。
8 the JoinField property is used to combine the name (question, answer, comment or vote) of the relation with the parent id. The generic type must be the same as the @Id annotated property.

Spring Data Elasticsearch 将为此类生成以下映射:

Spring Data Elasticsearch will build the following mapping for this class:

{
  "statements": {
    "mappings": {
      "properties": {
        "_class": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "routing": {
          "type": "keyword"
        },
        "relation": {
          "type": "join",
          "eager_global_ordinals": true,
          "relations": {
            "question": [
              "answer",
              "comment"
            ],
            "answer": "vote"
          }
        },
        "text": {
          "type": "text"
        }
      }
    }
  }
}

Storing data

给定此类的存储库,以下代码将插入一个问题、两个答案、一个注释和一个投票:

Given a repository for this class the following code inserts a question, two answers, a comment and a vote:

void init() {
    repository.deleteAll();

    Statement savedWeather = repository.save(
        Statement.builder()
            .withText("How is the weather?")
            .withRelation(new JoinField<>("question"))                          1
            .build());

    Statement sunnyAnswer = repository.save(
        Statement.builder()
            .withText("sunny")
            .withRelation(new JoinField<>("answer", savedWeather.getId()))      2
            .build());

    repository.save(
        Statement.builder()
            .withText("rainy")
            .withRelation(new JoinField<>("answer", savedWeather.getId()))      3
            .build());

    repository.save(
        Statement.builder()
            .withText("I don't like the rain")
            .withRelation(new JoinField<>("comment", savedWeather.getId()))     4
            .build());

    repository.save(
        Statement.builder()
            .withText("+1 for the sun")
            ,withRouting(savedWeather.getId())
            .withRelation(new JoinField<>("vote", sunnyAnswer.getId()))         5
            .build());
}
1 create a question statement
2 该问题的第一答案
3 the first answer to the question
4 the second answer
5 该问题的评论
6 a comment to the question
7 对第一个答案投赞成票,这需要将路由设置到 weather 文档,请参阅 Routing values
8 a vote for the first answer, this needs to have the routing set to the weather document, see Routing values.

Retrieving data

当前必须使用 native 查询来查询数据,因此不支持标准存储库方法。可以改用 repositories/custom-implementations.adoc

Currently native queries must be used to query the data, so there is no support from standard repository methods. repositories/custom-implementations.adoc can be used instead.

以下代码展示了一个示例,说明如何使用 ElasticsearchOperations 实例来检索所有具有 投票 的条目(必须是 答案,因为只有答案才可能获得投票):

The following code shows as an example how to retrieve all entries that have a vote (which must be answers, because only answers can have a vote) using an ElasticsearchOperations instance:

SearchHits<Statement> hasVotes() {

	Query query = NativeQuery.builder()
		.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb
			.hasChild(hc -> hc
				.queryName("vote")
				.query(matchAllQueryAsQuery())
				.scoreMode(ChildScoreMode.None)
			)))
		.build();

	return operations.search(query, Statement.class);
}