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 for routing related info see Routing values
2 a question can have answers and comments
3 an answer can have votes
4 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 the first answer to the question
3 the second answer
4 a comment to the question
5 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);
}