Skip to content

Commit 1710bd4

Browse files
committed
DATAES-644 - Add routing parameter to ElasticsearchOperations.
1 parent 80a50a8 commit 1710bd4

33 files changed

+1288
-292
lines changed

Diff for: src/main/asciidoc/index.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ include::reference/elasticsearch-auditing.adoc[]
3333
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
3434
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
3535

36+
include::reference/elasticsearch-join-types.adoc[]
37+
include::reference/elasticsearch-routing.adoc[]
3638
include::reference/elasticsearch-misc.adoc[]
3739
:leveloffset: -1
3840

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
[[elasticsearch.jointype]]
2+
= Join-Type implementation
3+
4+
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.
5+
6+
== Setting up the data
7+
8+
For an entity to be used in a parent child join relationship, it must have a property of type `JoinField` which must be annotated.
9+
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):
10+
11+
====
12+
[source,java]
13+
----
14+
@Document(indexName = "statements", routing="routing") <.>
15+
public class Statement {
16+
@Id
17+
private String id;
18+
19+
@Field(type = FieldType.Text)
20+
private String text;
21+
22+
@Field(type = FieldType.Keyword)
23+
private String routing;
24+
25+
@JoinTypeRelations(
26+
relations =
27+
{
28+
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}), <.>
29+
@JoinTypeRelation(parent = "answer", children = "vote") <.>
30+
}
31+
)
32+
private JoinField<String> relation; <.>
33+
34+
private Statement() {
35+
}
36+
37+
public static StatementBuilder builder() {
38+
return new StatementBuilder();
39+
}
40+
41+
public String getId() {
42+
return id;
43+
}
44+
45+
public void setId(String id) {
46+
this.id = id;
47+
}
48+
49+
public String getRouting() {
50+
return routing;
51+
}
52+
53+
public void setRouting(Routing routing) {
54+
this.routing = routing;
55+
}
56+
57+
public String getText() {
58+
return text;
59+
}
60+
61+
public void setText(String text) {
62+
this.text = text;
63+
}
64+
65+
public JoinField<String> getRelation() {
66+
return relation;
67+
}
68+
69+
public void setRelation(JoinField<String> relation) {
70+
this.relation = relation;
71+
}
72+
73+
public static final class StatementBuilder {
74+
private String id;
75+
private String text;
76+
private String routing;
77+
private JoinField<String> relation;
78+
79+
private StatementBuilder() {
80+
}
81+
82+
public StatementBuilder withId(String id) {
83+
this.id = id;
84+
return this;
85+
}
86+
87+
public StatementBuilder withRouting(String routing) {
88+
this.routing = routing;
89+
return this;
90+
}
91+
92+
public StatementBuilder withText(String text) {
93+
this.text = text;
94+
return this;
95+
}
96+
97+
public StatementBuilder withRelation(JoinField<String> relation) {
98+
this.relation = relation;
99+
return this;
100+
}
101+
102+
public Statement build() {
103+
Statement statement = new Statement();
104+
statement.setId(id);
105+
statement.setRouting(routing);
106+
statement.setText(text);
107+
statement.setRelation(relation);
108+
return statement;
109+
}
110+
}
111+
}
112+
----
113+
<.> for routing related info see <<elasticsearch.routing>>
114+
<.> a question can have answers and comments
115+
<.> an answer can have votes
116+
<.> the `JoinField` property is used to combine the name (_question_, _answer_, _comment_ or _vote_) of the relation with the parent id.
117+
The generic type must be the same as the `@Id` annotated property.
118+
====
119+
120+
Spring Data Elasticsearch will build the following mapping for this class:
121+
122+
====
123+
[source,json]
124+
----
125+
{
126+
"statements": {
127+
"mappings": {
128+
"properties": {
129+
"_class": {
130+
"type": "text",
131+
"fields": {
132+
"keyword": {
133+
"type": "keyword",
134+
"ignore_above": 256
135+
}
136+
}
137+
},
138+
"routing": {
139+
"type": "keyword"
140+
},
141+
"relation": {
142+
"type": "join",
143+
"eager_global_ordinals": true,
144+
"relations": {
145+
"question": [
146+
"answer",
147+
"comment"
148+
],
149+
"answer": "vote"
150+
}
151+
},
152+
"text": {
153+
"type": "text"
154+
}
155+
}
156+
}
157+
}
158+
}
159+
----
160+
====
161+
162+
== Storing data
163+
164+
Given a repository for this class the following code inserts a question, two answers, a comment and a vote:
165+
166+
====
167+
[source,java]
168+
----
169+
void init() {
170+
repository.deleteAll();
171+
172+
Statement savedWeather = repository.save(
173+
Statement.builder()
174+
.withText("How is the weather?")
175+
.withRelation(new JoinField<>("question")) <1>
176+
.build());
177+
178+
Statement sunnyAnswer = repository.save(
179+
Statement.builder()
180+
.withText("sunny")
181+
.withRelation(new JoinField<>("answer", savedWeather.getId())) <2>
182+
.build());
183+
184+
repository.save(
185+
Statement.builder()
186+
.withText("rainy")
187+
.withRelation(new JoinField<>("answer", savedWeather.getId())) <3>
188+
.build());
189+
190+
repository.save(
191+
Statement.builder()
192+
.withText("I don't like the rain")
193+
.withRelation(new JoinField<>("comment", savedWeather.getId())) <4>
194+
.build());
195+
196+
repository.save(
197+
Statement.builder()
198+
.withText("+1 for the sun")
199+
,withRouting(savedWeather.getId())
200+
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
201+
.build());
202+
}
203+
----
204+
<1> create a question statement
205+
<2> the first answer to the question
206+
<3> the second answer
207+
<4> a comment to the question
208+
<5> a vote for the first answer, this needs to have the routing set to the weather document, see <<elasticsearch.routing>>.
209+
====
210+
211+
== Retrieving data
212+
213+
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.
214+
215+
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:
216+
217+
====
218+
[source,java]
219+
----
220+
SearchHits<Statement> hasVotes() {
221+
NativeSearchQuery query = new NativeSearchQueryBuilder()
222+
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
223+
.build();
224+
225+
return operations.search(query, Statement.class);
226+
}
227+
----
228+
====

0 commit comments

Comments
 (0)