DATAES-899 - Add documentation for join-type.

original PR: #501
This commit is contained in:
Peter-Josef Meisch 2020-08-10 21:58:57 +02:00 committed by GitHub
parent fd23c10c16
commit 73bf3dd988
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 214 additions and 17 deletions

View File

@ -99,4 +99,208 @@ If the class to be retrieved has a `GeoPoint` property named _location_, the fol
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247))) Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
---- ----
[[elasticsearch.misc.jointype]]
== Join-Type implementation
Spring Data Elasticsearch supports the https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html[Join data type] for creating the corresponding index mappings and for storing the relevant information.
=== Setting up the data
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):
====
[source,java]
----
@Document(indexName = "statements")
public class Statement {
@Id
private String id;
@Field(type = FieldType.Text)
private String text;
@JoinTypeRelations(
relations =
{
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}), <1>
@JoinTypeRelation(parent = "answer", children = "vote") <2>
}
)
private JoinField<String> relation; <3>
private Statement() {
}
public static StatementBuilder builder() {
return new StatementBuilder();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
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 JoinField<String> relation;
private StatementBuilder() {
}
public StatementBuilder withId(String id) {
this.id = id;
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.setText(text);
statement.setRelation(relation);
return statement;
}
}
}
----
<1> a question can have answers and comments
<2> an answer can have votes
<3> 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 will build the following mapping for this class:
====
[source,json]
----
{
"statements": {
"mappings": {
"properties": {
"_class": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"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:
====
[source,java]
----
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")
.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
====
=== Retrieving data
Currently native search queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
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:
====
[source,java]
----
SearchHits<Statement> hasVotes() {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
.build();
return operations.search(query, Statement.class);
}
----
====

View File

@ -15,10 +15,6 @@
*/ */
package org.springframework.data.elasticsearch.core.join; package org.springframework.data.elasticsearch.core.join;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
@ -56,12 +52,4 @@ public class JoinField<ID> {
public String getName() { public String getName() {
return name; return name;
} }
public Map<String, Object> getAsMap() {
Map<String, Object> joinMap = new HashMap<>();
joinMap.put("name", getName());
joinMap.put("parent", getParent());
return Collections.unmodifiableMap(joinMap);
}
} }

View File

@ -3379,7 +3379,7 @@ public abstract class ElasticsearchTemplateTests {
void shouldUpdateEntityWithJoinFields(String qId1, String qId2, String aId1, String aId2) throws Exception { void shouldUpdateEntityWithJoinFields(String qId1, String qId2, String aId1, String aId2) throws Exception {
org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document
.create(); .create();
document.put("myJoinField", new JoinField<>("answer", qId2).getAsMap()); document.put("myJoinField", toDocument(new JoinField<>("answer", qId2)));
UpdateQuery updateQuery = UpdateQuery.builder(aId2) // UpdateQuery updateQuery = UpdateQuery.builder(aId2) //
.withDocument(document) // .withDocument(document) //
.withRouting(qId2).build(); .withRouting(qId2).build();
@ -3389,8 +3389,7 @@ public abstract class ElasticsearchTemplateTests {
// when // when
operations.bulkUpdate(queries, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)); operations.bulkUpdate(queries, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY));
indexOperations.refresh(); operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).refresh();
Thread.sleep(5000);
SearchHits<SampleJoinEntity> updatedHits = operations.search( SearchHits<SampleJoinEntity> updatedHits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(), new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(),
@ -3423,8 +3422,7 @@ public abstract class ElasticsearchTemplateTests {
Query query = new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).withRoute(qId2) Query query = new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).withRoute(qId2)
.build(); .build();
operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)); operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY));
indexOperations.refresh(); operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).refresh();
Thread.sleep(5000);
SearchHits<SampleJoinEntity> deletedHits = operations.search( SearchHits<SampleJoinEntity> deletedHits = operations.search(
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(), new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(),
@ -3439,6 +3437,13 @@ public abstract class ElasticsearchTemplateTests {
assertThat(hitIds.size()).isEqualTo(0); assertThat(hitIds.size()).isEqualTo(0);
} }
private org.springframework.data.elasticsearch.core.document.Document toDocument(JoinField joinField) {
org.springframework.data.elasticsearch.core.document.Document document = create();
document.put("name", joinField.getName());
document.put("parent", joinField.getParent());
return document;
}
protected RequestFactory getRequestFactory() { protected RequestFactory getRequestFactory() {
return ((AbstractElasticsearchTemplate) operations).getRequestFactory(); return ((AbstractElasticsearchTemplate) operations).getRequestFactory();
} }