mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-22 12:02:10 +00:00
Add routing parameter to ElasticsearchOperations.
Original Pull Request #562 Closes #1218
This commit is contained in:
parent
aba14c5e11
commit
89d6ae7f49
@ -33,6 +33,8 @@ include::reference/elasticsearch-auditing.adoc[]
|
||||
include::{spring-data-commons-docs}/entity-callbacks.adoc[]
|
||||
include::reference/elasticsearch-entity-callbacks.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/elasticsearch-join-types.adoc[]
|
||||
include::reference/elasticsearch-routing.adoc[]
|
||||
include::reference/elasticsearch-misc.adoc[]
|
||||
:leveloffset: -1
|
||||
|
||||
|
229
src/main/asciidoc/reference/elasticsearch-join-types.adoc
Normal file
229
src/main/asciidoc/reference/elasticsearch-join-types.adoc
Normal file
@ -0,0 +1,229 @@
|
||||
[[elasticsearch.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")
|
||||
@Routing("routing") <.>
|
||||
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"}), <.>
|
||||
@JoinTypeRelation(parent = "answer", children = "vote") <.>
|
||||
}
|
||||
)
|
||||
private JoinField<String> relation; <.>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<.> for routing related info see <<elasticsearch.routing>>
|
||||
<.> a question can have answers and comments
|
||||
<.> an answer can have votes
|
||||
<.> 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
|
||||
}
|
||||
}
|
||||
},
|
||||
"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:
|
||||
|
||||
====
|
||||
[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")
|
||||
,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 <<elasticsearch.routing>>.
|
||||
====
|
||||
|
||||
== 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);
|
||||
}
|
||||
----
|
||||
====
|
@ -1,7 +1,8 @@
|
||||
[[elasticsearch.misc]]
|
||||
= Miscellaneous Elasticsearch Operation Support
|
||||
|
||||
This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface. It is recommended to add those operations as custom implementation as described in <<repositories.custom-implementations>> .
|
||||
This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface.
|
||||
It is recommended to add those operations as custom implementation as described in <<repositories.custom-implementations>> .
|
||||
|
||||
[[elasticsearch.misc.filter]]
|
||||
== Filter Builder
|
||||
@ -27,7 +28,8 @@ Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, Sample
|
||||
[[elasticsearch.scroll]]
|
||||
== Using Scroll For Big Result Set
|
||||
|
||||
Elasticsearch has a scroll API for getting big result set in chunks. This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
|
||||
Elasticsearch has a scroll API for getting big result set in chunks.
|
||||
This is internally used by Spring Data Elasticsearch to provide the implementations of the `<T> SearchHitsIterator<T> SearchOperations.searchForStream(Query query, Class<T> clazz, IndexCoordinates index)` method.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@ -76,7 +78,8 @@ while (scroll.hasSearchHits()) {
|
||||
template.searchScrollClear(scrollId);
|
||||
----
|
||||
|
||||
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository. The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
|
||||
To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository.
|
||||
The implementation of the method will then use the scroll methods from the ElasticsearchTemplate.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@ -98,209 +101,3 @@ 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)))
|
||||
----
|
||||
|
||||
[[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);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
== New in Spring Data Elasticsearch 4.2
|
||||
|
||||
* Upgrade to Elasticsearch 7.10.0.
|
||||
* Support for custom routing values
|
||||
|
||||
[[new-features.4-1-0]]
|
||||
== New in Spring Data Elasticsearch 4.1
|
||||
|
106
src/main/asciidoc/reference/elasticsearch-routing.adoc
Normal file
106
src/main/asciidoc/reference/elasticsearch-routing.adoc
Normal file
@ -0,0 +1,106 @@
|
||||
|
||||
[[elasticsearch.routing]]
|
||||
= Routing values
|
||||
|
||||
When Elasticsearch stores a document in an index that has multiple shards, it determines the shard to you use based on the _id_ of the document.
|
||||
Sometimes it is necessary to predefine that multiple documents should be indexed on the same shard (join-types, faster search for related data).
|
||||
For this Elasticsearch offers the possibility to define a routing, which is the value that should be used to calculate the shard from instead of the _id_.
|
||||
|
||||
Spring Data Elasticsearch supports routing definitions on storing and retrieving data in the following ways:
|
||||
|
||||
== Routing on join-types
|
||||
|
||||
When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearch will automatically use the `parent` property of the entity's `JoinField` property as the value for the routing.
|
||||
|
||||
This is correct for all the use-cases where the parent-child relationship has just one level.
|
||||
If it is deeper, like a child-parent-grandparent relationship - like in the above example from _vote_ -> _answer_ -> _question_ - then the routing needs to explicitly specified by using the techniques described in the next section (the _vote_ needs the _question.id_ as routing value).
|
||||
|
||||
== Custom routing values
|
||||
|
||||
To define a custom routing for an entity, Spring Data Elasticsearch provides a `@Routing` annotation (reusing the `Statement` class from above):
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document(indexName = "statements")
|
||||
@Routing("routing") <.>
|
||||
public class Statement {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Field(type = FieldType.Text)
|
||||
private String text;
|
||||
|
||||
@JoinTypeRelations(
|
||||
relations =
|
||||
{
|
||||
@JoinTypeRelation(parent = "question", children = {"answer", "comment"}),
|
||||
@JoinTypeRelation(parent = "answer", children = "vote")
|
||||
}
|
||||
)
|
||||
private JoinField<String> relation;
|
||||
|
||||
@Nullable
|
||||
@Field(type = FieldType.Keyword)
|
||||
private String routing; <.>
|
||||
|
||||
// getter/setter...
|
||||
}
|
||||
----
|
||||
<.> This defines _"routing"_ as routing specification
|
||||
<.> a property with the name _routing_
|
||||
====
|
||||
|
||||
If the `routing` specification of the annotation is a plain string and not a SpEL expression, it is interpreted as the name of a property of the entity, in the example it's the _routing_ property.
|
||||
The value of this property will then be used as the routing value for all requests that use the entity.
|
||||
|
||||
It is also possible to us a SpEL expression in the `@Document` annotation like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document(indexName = "statements")
|
||||
@Routing("@myBean.getRouting(#entity)")
|
||||
public class Statement{
|
||||
// all the needed stuff
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
In this case the user needs to provide a bean with the name _myBean_ that has a method `String getRouting(Object)`. To reference the entity _"#entity"_ must be used in the SpEL expression, and the return value must be `null` or the routing value as a String.
|
||||
|
||||
If plain property's names and SpEL expressions are not enough to customize the routing definitions, it is possible to define provide an implementation of the `RoutingResolver` interface. This can then be set on the `ElasticOperations` instance:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
RoutingResolver resolver = ...;
|
||||
|
||||
ElasticsearchOperations customOperations= operations.withRouting(resolver);
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
The `withRouting()` functions return a copy of the original `ElasticsearchOperations` instance with the customized routing set.
|
||||
|
||||
|
||||
When a routing has been defined on an entity when it is stored in Elasticsearch, the same value must be provided when doing a _get_ or _delete_ operation. For methods that do not use an entity - like `get(ID)` or `delete(ID)` - the `ElasticsearchOperations.withRouting(RoutingResolver)` method can be used like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
String id = "someId";
|
||||
String routing = "theRoutingValue";
|
||||
|
||||
// get an entity
|
||||
Statement s = operations
|
||||
.withRouting(RoutingResolver.just(routing)) <.>
|
||||
.get(id, Statement.class);
|
||||
|
||||
// delete an entity
|
||||
operations.withRouting(RoutingResolver.just(routing)).delete(id);
|
||||
|
||||
----
|
||||
<.> `RoutingResolver.just(s)` returns a resolver that will just return the given String.
|
||||
====
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
|
||||
/**
|
||||
* Annotation to enable custom routing values for an entity.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
*/
|
||||
@Persistent
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface Routing {
|
||||
|
||||
/**
|
||||
* defines how the routing is determined. Can be either the name of a property or a SpEL expression. See the reference
|
||||
* documentation for examples how to use this annotation.
|
||||
*/
|
||||
String value();
|
||||
}
|
@ -22,6 +22,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -61,9 +62,12 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilde
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.NonNull;
|
||||
@ -85,6 +89,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Nullable private EntityOperations entityOperations;
|
||||
@Nullable private EntityCallbacks entityCallbacks;
|
||||
@Nullable private RefreshPolicy refreshPolicy;
|
||||
@Nullable protected RoutingResolver routingResolver;
|
||||
|
||||
// region Initialization
|
||||
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
|
||||
@ -92,12 +97,32 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
this.entityOperations = new EntityOperations(this.elasticsearchConverter.getMappingContext());
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = this.elasticsearchConverter.getMappingContext();
|
||||
this.entityOperations = new EntityOperations(mappingContext);
|
||||
this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext);
|
||||
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
VersionInfo.logVersions(getClusterVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return copy of this instance.
|
||||
*/
|
||||
private AbstractElasticsearchTemplate copy() {
|
||||
|
||||
AbstractElasticsearchTemplate copy = doCopy();
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
copy.setEntityCallbacks(entityCallbacks);
|
||||
}
|
||||
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
protected abstract AbstractElasticsearchTemplate doCopy();
|
||||
|
||||
protected ElasticsearchConverter createElasticsearchConverter() {
|
||||
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
|
||||
new SimpleElasticsearchMappingContext());
|
||||
@ -278,6 +303,19 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return this.delete(getEntityId(entity), index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
return doDelete(id, routingResolver.getRouting(), index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
final public String delete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
return doDelete(id, routing, index);
|
||||
}
|
||||
|
||||
protected abstract String doDelete(String id, @Nullable String routing, IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
|
||||
return bulkIndex(queries, getIndexCoordinatesFor(clazz));
|
||||
@ -621,7 +659,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Nullable
|
||||
private String getEntityId(Object entity) {
|
||||
|
||||
Object id = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getId();
|
||||
Object id = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
|
||||
.getId();
|
||||
|
||||
if (id != null) {
|
||||
return stringIdRepresentation(id);
|
||||
@ -632,13 +671,15 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
@Nullable
|
||||
public String getEntityRouting(Object entity) {
|
||||
return entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getRouting();
|
||||
return entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
|
||||
.getRouting();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long getEntityVersion(Object entity) {
|
||||
|
||||
Number version = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService()).getVersion();
|
||||
Number version = entityOperations.forEntity(entity, elasticsearchConverter.getConversionService(), routingResolver)
|
||||
.getVersion();
|
||||
|
||||
if (version != null && Long.class.isAssignableFrom(version.getClass())) {
|
||||
return ((Long) version);
|
||||
@ -651,7 +692,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
|
||||
|
||||
EntityOperations.AdaptibleEntity<Object> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
elasticsearchConverter.getConversionService());
|
||||
elasticsearchConverter.getConversionService(), routingResolver);
|
||||
return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null;
|
||||
}
|
||||
|
||||
@ -868,4 +909,24 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region routing
|
||||
private void setRoutingResolver(RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||
|
||||
this.routingResolver = routingResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchOperations withRouting(RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||
|
||||
AbstractElasticsearchTemplate copy = copy();
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
return copy;
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -227,9 +228,7 @@ public interface DocumentOperations {
|
||||
* @param index the index from which to delete
|
||||
* @return documentId of the document deleted
|
||||
*/
|
||||
default String delete(String id, IndexCoordinates index) {
|
||||
return delete(id, null, index);
|
||||
}
|
||||
String delete(String id, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete the one object with provided id.
|
||||
@ -239,7 +238,10 @@ public interface DocumentOperations {
|
||||
* @param index the index from which to delete
|
||||
* @return documentId of the document deleted
|
||||
* @since 4.1
|
||||
* @deprecated since 4.2, use {@link ElasticsearchOperations#withRouting(RoutingResolver)} and
|
||||
* {@link #delete(String, IndexCoordinates)}
|
||||
*/
|
||||
@Deprecated
|
||||
String delete(String id, @Nullable String routing, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -416,4 +417,16 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
return Objects.toString(id, null);
|
||||
}
|
||||
// endregion
|
||||
|
||||
//region routing
|
||||
/**
|
||||
* Returns a copy of this instance with the same configuration, but that uses a different {@link RoutingResolver} to
|
||||
* obtain routing information.
|
||||
*
|
||||
* @param routingResolver the {@link RoutingResolver} value, must not be {@literal null}.
|
||||
* @return DocumentOperations instance
|
||||
* @since 4.2
|
||||
*/
|
||||
ElasticsearchOperations withRouting(RoutingResolver routingResolver);
|
||||
//endregion
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
|
||||
|
||||
private final RestHighLevelClient client;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
// region Initialization
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
||||
@ -101,7 +101,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
initialize(createElasticsearchConverter());
|
||||
}
|
||||
@ -111,11 +110,15 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
initialize(elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractElasticsearchTemplate doCopy() {
|
||||
return new ElasticsearchRestTemplate(client, elasticsearchConverter);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region IndexOperations
|
||||
@ -155,7 +158,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequest request = requestFactory.getRequest(id, index);
|
||||
GetRequest request = requestFactory.getRequest(id,routingResolver.getRouting(), index);
|
||||
GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT));
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
@ -177,7 +180,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String id, IndexCoordinates index) {
|
||||
GetRequest request = requestFactory.getRequest(id, index);
|
||||
GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(),index);
|
||||
request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
|
||||
return execute(client -> client.get(request, RequestOptions.DEFAULT).isExists());
|
||||
}
|
||||
@ -192,7 +195,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
@ -112,6 +112,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
this.client = client;
|
||||
initialize(elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractElasticsearchTemplate doCopy() {
|
||||
ElasticsearchTemplate elasticsearchTemplate = new ElasticsearchTemplate(client, elasticsearchConverter);
|
||||
elasticsearchTemplate.setSearchTimeout(searchTimeout);
|
||||
return elasticsearchTemplate;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region IndexOperations
|
||||
@ -170,7 +178,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index);
|
||||
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, routingResolver.getRouting(), index);
|
||||
GetResponse response = getRequestBuilder.execute().actionGet();
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
@ -192,7 +201,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String id, IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index);
|
||||
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, routingResolver.getRouting(), index);
|
||||
getRequestBuilder.setFetchSource(false);
|
||||
return getRequestBuilder.execute().actionGet().isExists();
|
||||
}
|
||||
@ -207,7 +217,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
@ -23,6 +23,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
@ -72,14 +73,15 @@ class EntityOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService}.
|
||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService} and {@link RoutingResolver}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @return
|
||||
* @param routingResolver the {@link RoutingResolver}, must not be {@literal null}
|
||||
* @return the {@link AdaptibleEntity}
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
<T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService) {
|
||||
<T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService, RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
@ -88,7 +90,7 @@ class EntityOperations {
|
||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||
}
|
||||
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService);
|
||||
return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,7 +306,7 @@ class EntityOperations {
|
||||
|
||||
/**
|
||||
* returns the routing for the entity if it is available
|
||||
*
|
||||
*
|
||||
* @return routing if available
|
||||
* @since 4.1
|
||||
*/
|
||||
@ -464,7 +466,7 @@ class EntityOperations {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.UnmappedEntity#getId()
|
||||
*/
|
||||
@ -546,32 +548,37 @@ class EntityOperations {
|
||||
*/
|
||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
||||
|
||||
private final T bean;
|
||||
private final ElasticsearchPersistentEntity<?> entity;
|
||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
private final IdentifierAccessor identifierAccessor;
|
||||
private final ConversionService conversionService;
|
||||
private final RoutingResolver routingResolver;
|
||||
|
||||
private AdaptibleMappedEntity(ElasticsearchPersistentEntity<?> entity, IdentifierAccessor identifierAccessor,
|
||||
ConvertingPropertyAccessor<T> propertyAccessor, ConversionService conversionService) {
|
||||
private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity<?> entity,
|
||||
IdentifierAccessor identifierAccessor, ConvertingPropertyAccessor<T> propertyAccessor,
|
||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||
|
||||
super(entity, identifierAccessor, propertyAccessor);
|
||||
|
||||
this.bean = bean;
|
||||
this.entity = entity;
|
||||
this.propertyAccessor = propertyAccessor;
|
||||
this.identifierAccessor = identifierAccessor;
|
||||
this.conversionService = conversionService;
|
||||
this.routingResolver = routingResolver;
|
||||
}
|
||||
|
||||
static <T> AdaptibleEntity<T> of(T bean,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context,
|
||||
ConversionService conversionService) {
|
||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = context.getRequiredPersistentEntity(bean.getClass());
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||
|
||||
return new AdaptibleMappedEntity<>(entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService);
|
||||
return new AdaptibleMappedEntity<>(bean, entity, identifierAccessor,
|
||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -616,7 +623,7 @@ class EntityOperations {
|
||||
public Number getVersion() {
|
||||
|
||||
ElasticsearchPersistentProperty versionProperty = entity.getVersionProperty();
|
||||
return versionProperty != null ? propertyAccessor.getProperty(versionProperty, Number.class) : null;
|
||||
return versionProperty != null ? propertyAccessor.getProperty(versionProperty, Number.class) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -661,6 +668,12 @@ class EntityOperations {
|
||||
@Override
|
||||
public String getRouting() {
|
||||
|
||||
String routing = routingResolver.getRouting(bean);
|
||||
|
||||
if (routing != null) {
|
||||
return routing;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentProperty joinFieldProperty = entity.getJoinFieldProperty();
|
||||
|
||||
if (joinFieldProperty != null) {
|
||||
@ -673,6 +686,7 @@ class EntityOperations {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ public interface ReactiveDocumentOperations {
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
* @deprecated since 4.0, use {@link #delete(String, Class)} or {@link #deleteById(String, IndexCoordinates)}
|
||||
* @deprecated since 4.0, use {@link #delete(String, Class)} or {@link #delete(String, IndexCoordinates)}
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<String> delete(String id, Class<?> entityType, IndexCoordinates index) {
|
||||
|
@ -20,6 +20,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -88,6 +89,17 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(Class<?> clazz);
|
||||
|
||||
//region routing
|
||||
/**
|
||||
* Returns a copy of this instance with the same configuration, but that uses a different {@link RoutingResolver} to
|
||||
* obtain routing information.
|
||||
*
|
||||
* @param routingResolver the {@link RoutingResolver} value, must not be {@literal null}.
|
||||
* @return DocumentOperations instance
|
||||
*/
|
||||
ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver);
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient}.
|
||||
|
@ -75,6 +75,8 @@ import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
@ -104,7 +106,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
private final ReactiveElasticsearchClient client;
|
||||
private final ElasticsearchConverter converter;
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final SimpleElasticsearchMappingContext mappingContext;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
private final EntityOperations operations;
|
||||
protected RequestFactory requestFactory;
|
||||
@ -114,18 +116,23 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
private @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||
|
||||
private RoutingResolver routingResolver;
|
||||
|
||||
// region Initialization
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) {
|
||||
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
|
||||
|
||||
Assert.notNull(client, "client must not be null");
|
||||
Assert.notNull(converter, "converter must not be null");
|
||||
|
||||
this.client = client;
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
|
||||
this.mappingContext = (SimpleElasticsearchMappingContext) converter.getMappingContext();
|
||||
this.routingResolver = new DefaultRoutingResolver(this.mappingContext);
|
||||
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
@ -134,6 +141,16 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
logVersions();
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchTemplate copy() {
|
||||
|
||||
ReactiveElasticsearchTemplate copy = new ReactiveElasticsearchTemplate(client, converter);
|
||||
copy.setRefreshPolicy(refreshPolicy);
|
||||
copy.setIndicesOptions(indicesOptions);
|
||||
copy.setEntityCallbacks(entityCallbacks);
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void logVersions() {
|
||||
getClusterVersion() //
|
||||
.doOnSuccess(VersionInfo::logVersions) //
|
||||
@ -254,7 +271,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
|
||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
|
||||
routingResolver);
|
||||
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass());
|
||||
@ -372,7 +390,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, index)));
|
||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -395,7 +413,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
private IndexQuery getIndexQuery(Object value) {
|
||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService());
|
||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService(), routingResolver);
|
||||
|
||||
Object id = entity.getId();
|
||||
IndexQuery query = new IndexQuery();
|
||||
@ -448,7 +466,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doGet(requestFactory.getRequest(id, index)));
|
||||
return Mono.defer(() -> doGet(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -470,7 +488,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
@Override
|
||||
public Mono<String> delete(Object entity, IndexCoordinates index) {
|
||||
|
||||
AdaptibleEntity<?> elasticsearchEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
AdaptibleEntity<?> elasticsearchEntity = operations.forEntity(entity, converter.getConversionService(),
|
||||
routingResolver);
|
||||
|
||||
if (elasticsearchEntity.getId() == null) {
|
||||
return Mono.error(new IllegalArgumentException("entity must have an id"));
|
||||
@ -503,7 +522,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return doDeleteById(id, null, index);
|
||||
return doDeleteById(id, routingResolver.getRouting(), index);
|
||||
}
|
||||
|
||||
private Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
@ -976,6 +995,25 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region routing
|
||||
private void setRoutingResolver(RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||
|
||||
this.routingResolver = routingResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||
|
||||
ReactiveElasticsearchTemplate copy = copy();
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
return copy;
|
||||
}
|
||||
// endregion
|
||||
|
||||
protected interface DocumentCallback<T> {
|
||||
|
||||
@NonNull
|
||||
|
@ -833,12 +833,17 @@ class RequestFactory {
|
||||
// endregion
|
||||
|
||||
// region get
|
||||
public GetRequest getRequest(String id, IndexCoordinates index) {
|
||||
return new GetRequest(index.getIndexName(), id);
|
||||
public GetRequest getRequest(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
GetRequest getRequest = new GetRequest(index.getIndexName(), id);
|
||||
getRequest.routing(routing);
|
||||
return getRequest;
|
||||
}
|
||||
|
||||
public GetRequestBuilder getRequestBuilder(Client client, String id, IndexCoordinates index) {
|
||||
return client.prepareGet(index.getIndexName(), null, id);
|
||||
public GetRequestBuilder getRequestBuilder(Client client, String id, @Nullable String routing,
|
||||
IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = client.prepareGet(index.getIndexName(), null, id);
|
||||
getRequestBuilder.setRouting(routing);
|
||||
return getRequestBuilder;
|
||||
}
|
||||
|
||||
public MultiGetRequest multiGetRequest(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
@ -44,17 +44,19 @@ public class SearchHit<T> {
|
||||
private final Map<String, List<String>> highlightFields = new LinkedHashMap<>();
|
||||
private final Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
@Nullable private String routing;
|
||||
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, T content) {
|
||||
this(index, id, score, sortValues, highlightFields, null, null, content);
|
||||
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
|
||||
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields, T content) {
|
||||
this(index, id, routing, score, sortValues, highlightFields, null, null, content);
|
||||
}
|
||||
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, @Nullable Map<String, SearchHits<?>> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData, T content) {
|
||||
public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score,
|
||||
@Nullable Object[] sortValues, @Nullable Map<String, List<String>> highlightFields,
|
||||
@Nullable Map<String, SearchHits<?>> innerHits, @Nullable NestedMetaData nestedMetaData, T content) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.routing = routing;
|
||||
this.score = score;
|
||||
this.sortValues = (sortValues != null) ? Arrays.asList(sortValues) : new ArrayList<>();
|
||||
|
||||
@ -165,4 +167,13 @@ public class SearchHit<T> {
|
||||
return "SearchHit{" + "id='" + id + '\'' + ", score=" + score + ", sortValues=" + sortValues + ", content="
|
||||
+ content + ", highlightFields=" + highlightFields + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the routing for this SearchHit, may be {@literal null}.
|
||||
* @since 4.2
|
||||
*/
|
||||
@Nullable
|
||||
public String getRouting() {
|
||||
return routing;
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ class SearchHitMapping<T> {
|
||||
|
||||
return new SearchHit<T>(searchDocument.getIndex(), //
|
||||
searchDocument.hasId() ? searchDocument.getId() : null, //
|
||||
searchDocument.getRouting(), //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
getHighlightsAndRemapFieldNames(searchDocument), //
|
||||
@ -189,6 +190,7 @@ class SearchHitMapping<T> {
|
||||
Object targetObject = converter.read(targetType, searchDocument);
|
||||
convertedSearchHits.add(new SearchHit<Object>(searchDocument.getIndex(), //
|
||||
searchDocument.getId(), //
|
||||
searchDocument.getRouting(), //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
searchDocument.getHighlightFields(), //
|
||||
|
@ -89,4 +89,13 @@ public interface SearchDocument extends Document {
|
||||
default NestedMetaData getNestedMetaData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the routing value for the document
|
||||
* @since 4.2
|
||||
*/
|
||||
@Nullable
|
||||
default String getRouting() {
|
||||
return getFieldValue("_routing");
|
||||
}
|
||||
}
|
||||
|
@ -171,9 +171,18 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
|
||||
|
||||
/**
|
||||
* returns the default settings for an index.
|
||||
*
|
||||
*
|
||||
* @return settings as {@link Document}
|
||||
* @since 4.1
|
||||
*/
|
||||
Document getDefaultSettings();
|
||||
|
||||
/**
|
||||
* Resolves the routing for a bean.
|
||||
*
|
||||
* @param bean the bean to resolve the routing for
|
||||
* @return routing value, may be {@literal null}
|
||||
*/
|
||||
@Nullable
|
||||
String resolveRouting(T bean);
|
||||
}
|
||||
|
@ -24,7 +24,9 @@ import org.elasticsearch.index.VersionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.annotations.Parent;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
import org.springframework.data.elasticsearch.annotations.Setting;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
@ -32,11 +34,11 @@ import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
@ -78,6 +80,8 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
private @Nullable VersionType versionType;
|
||||
private boolean createIndexAndMapping;
|
||||
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
|
||||
private @Nullable String routing;
|
||||
|
||||
private final ConcurrentHashMap<String, Expression> indexNameExpressions = new ConcurrentHashMap<>();
|
||||
private final Lazy<EvaluationContext> indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext);
|
||||
@ -102,12 +106,21 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
this.indexStoreType = document.indexStoreType();
|
||||
this.versionType = document.versionType();
|
||||
this.createIndexAndMapping = document.createIndex();
|
||||
|
||||
Setting setting = AnnotatedElementUtils.getMergedAnnotation(clazz, Setting.class);
|
||||
|
||||
if (setting != null) {
|
||||
this.settingPath = setting.settingPath();
|
||||
}
|
||||
}
|
||||
|
||||
Setting setting = AnnotatedElementUtils.getMergedAnnotation(clazz, Setting.class);
|
||||
Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class);
|
||||
|
||||
if (setting != null) {
|
||||
this.settingPath = setting.settingPath();
|
||||
if (routingAnnotation != null) {
|
||||
|
||||
Assert.hasText(routingAnnotation.value(), "@Routing annotation must contain a non-empty value");
|
||||
|
||||
this.routing = routingAnnotation.value();
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +201,8 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
return scoreProperty;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
public void addPersistentProperty(ElasticsearchPersistentProperty property) {
|
||||
super.addPersistentProperty(property);
|
||||
@ -351,14 +366,15 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
|
||||
Expression expression = getExpressionForIndexName(name);
|
||||
|
||||
String resolvedName = expression != null ? expression.getValue(indexNameEvaluationContext.get(), String.class) : null;
|
||||
String resolvedName = expression != null ? expression.getValue(indexNameEvaluationContext.get(), String.class)
|
||||
: null;
|
||||
return resolvedName != null ? resolvedName : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an {@link Expression} for #name if name contains a {@link ParserContext#TEMPLATE_EXPRESSION} otherwise
|
||||
* returns {@literal null}.
|
||||
*
|
||||
*
|
||||
* @param name the name to get the expression for
|
||||
* @return Expression may be null
|
||||
*/
|
||||
@ -373,7 +389,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
/**
|
||||
* build the {@link EvaluationContext} considering {@link ExpressionDependencies} from the name returned by
|
||||
* {@link #getIndexName()}.
|
||||
*
|
||||
*
|
||||
* @return EvaluationContext
|
||||
*/
|
||||
private EvaluationContext getIndexNameEvaluationContext() {
|
||||
@ -385,6 +401,36 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
return getEvaluationContext(null, expressionDependencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String resolveRouting(T bean) {
|
||||
|
||||
if (routing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentProperty persistentProperty = getPersistentProperty(routing);
|
||||
|
||||
if (persistentProperty != null) {
|
||||
Object propertyValue = getPropertyAccessor(bean).getProperty(persistentProperty);
|
||||
|
||||
return propertyValue != null ? propertyValue.toString() : null;
|
||||
}
|
||||
|
||||
try {
|
||||
Expression expression = routingExpressions.computeIfAbsent(routing, PARSER::parseExpression);
|
||||
ExpressionDependencies expressionDependencies = ExpressionDependencies.discover(expression);
|
||||
|
||||
EvaluationContext context = getEvaluationContext(null, expressionDependencies);
|
||||
context.setVariable("entity", bean);
|
||||
|
||||
return expression.getValue(context, String.class);
|
||||
} catch (EvaluationException e) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Could not resolve expression: " + routing + " for object of class " + bean.getClass().getCanonicalName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link RoutingResolver} interface. Returns {@literal null} for the non-bean method and
|
||||
* delegates to the corresponding persistent entity for the bean-method.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
*/
|
||||
public class DefaultRoutingResolver implements RoutingResolver {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity, ? extends ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
public DefaultRoutingResolver(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity, ? extends ElasticsearchPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> String getRouting(T bean) {
|
||||
|
||||
ElasticsearchPersistentEntity<T> persistentEntity = (ElasticsearchPersistentEntity<T>) mappingContext
|
||||
.getPersistentEntity(bean.getClass());
|
||||
|
||||
if (persistentEntity != null) {
|
||||
return persistentEntity.resolveRouting(bean);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface RoutingResolver {
|
||||
|
||||
/**
|
||||
* returns the routing when no entity is available.
|
||||
*
|
||||
* @return the routing value
|
||||
*/
|
||||
@Nullable
|
||||
String getRouting();
|
||||
|
||||
/**
|
||||
* Returns the routing for a bean.
|
||||
*
|
||||
* @param bean the bean to get the routing for
|
||||
* @return the routing value
|
||||
*/
|
||||
@Nullable
|
||||
<T> String getRouting(T bean);
|
||||
|
||||
/**
|
||||
* Returns a {@link RoutingResolver that always retuns a fixed value}
|
||||
*
|
||||
* @param value the value to return
|
||||
* @return the fixed-value {@link RoutingResolver}
|
||||
*/
|
||||
static RoutingResolver just(String value) {
|
||||
|
||||
Assert.notNull(value, "value must not be null");
|
||||
|
||||
return new RoutingResolver() {
|
||||
@Override
|
||||
public String getRouting() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRouting(Object bean) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* classes/interfaces for specification and implementation of Elasticsearch routing.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
@ -58,7 +58,6 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
* @author Don Wellington
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||
@DisplayName("ElasticsearchRestTemplate")
|
||||
public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests {
|
||||
|
@ -91,6 +91,7 @@ import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@ -119,6 +120,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Subhobrata Dey
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
public abstract class ElasticsearchTemplateTests {
|
||||
|
||||
protected static final String INDEX_NAME_JOIN_SAMPLE_ENTITY = "test-index-sample-join-template";
|
||||
@ -130,6 +132,7 @@ public abstract class ElasticsearchTemplateTests {
|
||||
@Autowired protected ElasticsearchOperations operations;
|
||||
protected IndexOperations indexOperations;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
indexOperations = operations.indexOps(SampleEntity.class);
|
||||
@ -3351,7 +3354,8 @@ public abstract class ElasticsearchTemplateTests {
|
||||
shouldDeleteEntityWithJoinFields(qId2, aId2);
|
||||
}
|
||||
|
||||
void shouldSaveEntityWithJoinFields(String qId1, String qId2, String aId1, String aId2) throws Exception {
|
||||
// #1218
|
||||
private void shouldSaveEntityWithJoinFields(String qId1, String qId2, String aId1, String aId2) throws Exception {
|
||||
SampleJoinEntity sampleQuestionEntity1 = new SampleJoinEntity();
|
||||
sampleQuestionEntity1.setUuid(qId1);
|
||||
sampleQuestionEntity1.setText("This is a question");
|
||||
@ -3391,18 +3395,18 @@ public abstract class ElasticsearchTemplateTests {
|
||||
new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId1)).build(),
|
||||
SampleJoinEntity.class);
|
||||
|
||||
List<String> hitIds = hits.getSearchHits().stream().map(new Function<SearchHit<SampleJoinEntity>, String>() {
|
||||
@Override
|
||||
public String apply(SearchHit<SampleJoinEntity> sampleJoinEntitySearchHit) {
|
||||
return sampleJoinEntitySearchHit.getId();
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
List<String> hitIds = hits.getSearchHits().stream()
|
||||
.map(sampleJoinEntitySearchHit -> sampleJoinEntitySearchHit.getId()).collect(Collectors.toList());
|
||||
|
||||
assertThat(hitIds.size()).isEqualTo(2);
|
||||
assertThat(hitIds.containsAll(Arrays.asList(aId1, aId2))).isTrue();
|
||||
|
||||
hits.forEach(searchHit -> {
|
||||
assertThat(searchHit.getRouting()).isEqualTo(qId1);
|
||||
});
|
||||
}
|
||||
|
||||
void shouldUpdateEntityWithJoinFields(String qId1, String qId2, String aId1, String aId2) throws Exception {
|
||||
private 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
|
||||
.create();
|
||||
document.put("myJoinField", toDocument(new JoinField<>("answer", qId2)));
|
||||
@ -3444,7 +3448,7 @@ public abstract class ElasticsearchTemplateTests {
|
||||
assertThat(hitIds.get(0)).isEqualTo(aId1);
|
||||
}
|
||||
|
||||
void shouldDeleteEntityWithJoinFields(String qId2, String aId2) throws Exception {
|
||||
private void shouldDeleteEntityWithJoinFields(String qId2, String aId2) throws Exception {
|
||||
Query query = new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).withRoute(qId2)
|
||||
.build();
|
||||
operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY));
|
||||
@ -3822,4 +3826,5 @@ public abstract class ElasticsearchTemplateTests {
|
||||
@JoinTypeRelation(parent = "question", children = { "answer" }) }) private JoinField<String> myJoinField;
|
||||
@Field(type = Text) private String text;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,14 +48,12 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
|
||||
@DisplayName("ElasticsearchTransportTemplate")
|
||||
public class ElasticsearchTransportTemplateTests extends ElasticsearchTemplateTests {
|
||||
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
class EntityOperationsTest {
|
||||
|
||||
@Nullable private static ConversionService conversionService;
|
||||
@Nullable private static EntityOperations entityOperations;
|
||||
@Nullable private static SimpleElasticsearchMappingContext mappingContext;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpAll() {
|
||||
mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setInitialEntitySet(new HashSet<>(Arrays.asList(EntityWithRouting.class)));
|
||||
mappingContext.afterPropertiesSet();
|
||||
entityOperations = new EntityOperations(mappingContext);
|
||||
|
||||
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext,
|
||||
new GenericConversionService());
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
conversionService = converter.getConversionService();
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should return routing from DefaultRoutingAccessor")
|
||||
void shouldReturnRoutingFromDefaultRoutingAccessor() {
|
||||
|
||||
EntityWithRouting entity = EntityWithRouting.builder().id("42").routing("theRoute").build();
|
||||
EntityOperations.AdaptibleEntity<EntityWithRouting> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
conversionService, new DefaultRoutingResolver(mappingContext));
|
||||
|
||||
String routing = adaptibleEntity.getRouting();
|
||||
|
||||
assertThat(routing).isEqualTo("theRoute");
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should return routing from JoinField when routing value is null")
|
||||
void shouldReturnRoutingFromJoinFieldWhenRoutingValueIsNull() {
|
||||
|
||||
EntityWithRoutingAndJoinField entity = EntityWithRoutingAndJoinField.builder().id("42")
|
||||
.joinField(new JoinField<>("foo", "foo-routing")).build();
|
||||
|
||||
EntityOperations.AdaptibleEntity<EntityWithRoutingAndJoinField> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
conversionService, new DefaultRoutingResolver(mappingContext));
|
||||
|
||||
String routing = adaptibleEntity.getRouting();
|
||||
|
||||
assertThat(routing).isEqualTo("foo-routing");
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should return routing from routing when JoinField is set")
|
||||
void shouldReturnRoutingFromRoutingWhenJoinFieldIsSet() {
|
||||
EntityWithRoutingAndJoinField entity = EntityWithRoutingAndJoinField.builder().id("42").routing("theRoute")
|
||||
.joinField(new JoinField<>("foo", "foo-routing")).build();
|
||||
|
||||
EntityOperations.AdaptibleEntity<EntityWithRoutingAndJoinField> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
conversionService, new DefaultRoutingResolver(mappingContext));
|
||||
|
||||
String routing = adaptibleEntity.getRouting();
|
||||
|
||||
assertThat(routing).isEqualTo("theRoute");
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "entity-operations-test")
|
||||
@Routing("routing")
|
||||
static class EntityWithRouting {
|
||||
@Id private String id;
|
||||
private String routing;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "entity-operations-test")
|
||||
@Routing("routing")
|
||||
static class EntityWithRoutingAndJoinField {
|
||||
@Id private String id;
|
||||
private String routing;
|
||||
private JoinField<String> joinField;
|
||||
}
|
||||
}
|
@ -59,11 +59,11 @@ class SearchHitSupportTest {
|
||||
void shouldReturnTheSameListInstanceInSearchHitsAndGetContent() {
|
||||
|
||||
List<SearchHit<String>> hits = new ArrayList<>();
|
||||
hits.add(new SearchHit<>(null, null, 0, null, null, "one"));
|
||||
hits.add(new SearchHit<>(null, null, 0, null, null, "two"));
|
||||
hits.add(new SearchHit<>(null, null, 0, null, null, "three"));
|
||||
hits.add(new SearchHit<>(null, null, 0, null, null, "four"));
|
||||
hits.add(new SearchHit<>(null, null, 0, null, null, "five"));
|
||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, "one"));
|
||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, "two"));
|
||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, "three"));
|
||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, "four"));
|
||||
hits.add(new SearchHit<>(null, null, null, 0, null, null, "five"));
|
||||
|
||||
SearchHits<String> originalSearchHits = new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, "scroll",
|
||||
hits, null);
|
||||
@ -112,7 +112,7 @@ class SearchHitSupportTest {
|
||||
@Override
|
||||
public SearchHit<String> next() {
|
||||
String nextString = iterator.next();
|
||||
return new SearchHit<>("index", "id", 1.0f, new Object[0], emptyMap(), nextString);
|
||||
return new SearchHit<>("index", "id", null, 1.0f, new Object[0], emptyMap(), nextString);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class StreamQueriesTest {
|
||||
|
||||
// given
|
||||
List<SearchHit<String>> hits = new ArrayList<>();
|
||||
hits.add(new SearchHit<String>(null, null, 0, null, null, "one"));
|
||||
hits.add(getOneSearchHit());
|
||||
|
||||
SearchScrollHits<String> searchHits = newSearchScrollHits(hits, "1234");
|
||||
|
||||
@ -61,12 +61,16 @@ public class StreamQueriesTest {
|
||||
|
||||
}
|
||||
|
||||
private SearchHit<String> getOneSearchHit() {
|
||||
return new SearchHit<String>(null, null, null, 0, null, null, "one");
|
||||
}
|
||||
|
||||
@Test // DATAES-766
|
||||
public void shouldReturnTotalHits() {
|
||||
|
||||
// given
|
||||
List<SearchHit<String>> hits = new ArrayList<>();
|
||||
hits.add(new SearchHit<String>(null, null, 0, null, null, "one"));
|
||||
hits.add(getOneSearchHit());
|
||||
|
||||
SearchScrollHits<String> searchHits = newSearchScrollHits(hits, "1234");
|
||||
|
||||
@ -85,12 +89,9 @@ public class StreamQueriesTest {
|
||||
@Test // DATAES-817
|
||||
void shouldClearAllScrollIds() {
|
||||
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits4 = newSearchScrollHits(Collections.emptyList(), "s-3");
|
||||
|
||||
Iterator<SearchScrollHits<String>> searchScrollHitsIterator = Arrays
|
||||
@ -114,12 +115,9 @@ public class StreamQueriesTest {
|
||||
@Test // DATAES-831
|
||||
void shouldReturnAllForRequestedSizeOf0() {
|
||||
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits4 = newSearchScrollHits(Collections.emptyList(), "s-3");
|
||||
|
||||
Iterator<SearchScrollHits<String>> searchScrollHitsIterator = Arrays
|
||||
@ -139,12 +137,9 @@ public class StreamQueriesTest {
|
||||
@Test // DATAES-831
|
||||
void shouldOnlyReturnRequestedCount() {
|
||||
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(
|
||||
Collections.singletonList(new SearchHit<String>(null, null, 0, null, null, "one")), "s-2");
|
||||
SearchScrollHits<String> searchHits1 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-1");
|
||||
SearchScrollHits<String> searchHits2 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits3 = newSearchScrollHits(Collections.singletonList(getOneSearchHit()), "s-2");
|
||||
SearchScrollHits<String> searchHits4 = newSearchScrollHits(Collections.emptyList(), "s-3");
|
||||
|
||||
Iterator<SearchScrollHits<String>> searchScrollHitsIterator = Arrays
|
||||
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringJUnitConfig({ DefaultRoutingResolverUnitTest.Config.class })
|
||||
class DefaultRoutingResolverUnitTest {
|
||||
|
||||
@Autowired private ApplicationContext applicationContext;
|
||||
private SimpleElasticsearchMappingContext mappingContext;
|
||||
|
||||
@Nullable private RoutingResolver routingResolver;
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
@Bean
|
||||
SpelRouting spelRouting() {
|
||||
return new SpelRouting();
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setApplicationContext(applicationContext);
|
||||
|
||||
routingResolver = new DefaultRoutingResolver(mappingContext);
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should throw an exception on unknown property")
|
||||
void shouldThrowAnExceptionOnUnknownProperty() {
|
||||
|
||||
InvalidRoutingEntity entity = new InvalidRoutingEntity("42", "route 66");
|
||||
|
||||
assertThatThrownBy(() -> routingResolver.getRouting(entity)).isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should return the routing from the entity")
|
||||
void shouldReturnTheRoutingFromTheEntity() {
|
||||
|
||||
ValidRoutingEntity entity = new ValidRoutingEntity("42", "route 66");
|
||||
|
||||
String routing = routingResolver.getRouting(entity);
|
||||
|
||||
assertThat(routing).isEqualTo("route 66");
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should return routing from SpEL expression")
|
||||
void shouldReturnRoutingFromSpElExpression() {
|
||||
|
||||
ValidSpelRoutingEntity entity = new ValidSpelRoutingEntity("42", "route 42");
|
||||
|
||||
String routing = routingResolver.getRouting(entity);
|
||||
|
||||
assertThat(routing).isEqualTo("route 42");
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "routing-resolver-test")
|
||||
@Routing("theRouting")
|
||||
static class ValidRoutingEntity {
|
||||
@Id private String id;
|
||||
private String theRouting;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "routing-resolver-test")
|
||||
@Routing(value = "@spelRouting.getRouting(#entity)")
|
||||
static class ValidSpelRoutingEntity {
|
||||
@Id private String id;
|
||||
private String theRouting;
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "routing-resolver-test")
|
||||
@Routing("unknownProperty")
|
||||
static class InvalidRoutingEntity {
|
||||
@Id private String id;
|
||||
private String theRouting;
|
||||
}
|
||||
|
||||
static class SpelRouting {
|
||||
|
||||
@Nullable
|
||||
public String getRouting(Object o) {
|
||||
|
||||
if (o instanceof ValidSpelRoutingEntity) {
|
||||
return ((ValidSpelRoutingEntity) o).getTheRouting();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.elasticsearch.cluster.routing.Murmur3HashFunction;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||
public class ElasticsearchOperationsRoutingTests {
|
||||
|
||||
private static final String INDEX = "routing-test";
|
||||
private static final String ID_1 = "id1";
|
||||
private static final String ID_2 = "id2";
|
||||
private static final String ID_3 = "id3";
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
@Nullable private IndexOperations indexOps;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
// check that the used id values go to different shards of the index which is configured to have 5 shards.
|
||||
// Elasticsearch uses the following function:
|
||||
Function<String, Integer> calcShard = routing -> Math.floorMod(Murmur3HashFunction.hash(routing), 5);
|
||||
|
||||
Integer shard1 = calcShard.apply(ID_1);
|
||||
Integer shard2 = calcShard.apply(ID_2);
|
||||
Integer shard3 = calcShard.apply(ID_3);
|
||||
|
||||
assertThat(shard1).isNotEqualTo(shard2);
|
||||
assertThat(shard1).isNotEqualTo(shard3);
|
||||
assertThat(shard2).isNotEqualTo(shard3);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexOps = operations.indexOps(RoutingEntity.class);
|
||||
indexOps.delete();
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and be able to get it")
|
||||
void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity);
|
||||
indexOps.refresh();
|
||||
|
||||
RoutingEntity savedEntity = operations.withRouting(RoutingResolver.just(ID_2)).get(entity.id, RoutingEntity.class);
|
||||
|
||||
assertThat(savedEntity).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and be able to delete it")
|
||||
void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity);
|
||||
indexOps.refresh();
|
||||
|
||||
String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, IndexCoordinates.of(INDEX));
|
||||
|
||||
assertThat(deletedId).isEqualTo(entity.getId());
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and get the routing in the search result")
|
||||
void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity);
|
||||
indexOps.refresh();
|
||||
|
||||
SearchHits<RoutingEntity> searchHits = operations.search(Query.findAll(), RoutingEntity.class);
|
||||
|
||||
assertThat(searchHits.getSearchHits()).hasSize(1);
|
||||
assertThat(searchHits.getSearchHit(0).getRouting()).isEqualTo(ID_2);
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document(indexName = INDEX, shards = 5)
|
||||
@Routing("routing")
|
||||
static class RoutingEntity {
|
||||
@Id private String id;
|
||||
private String routing;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
|
||||
public class ElasticsearchOperationsRoutingTransportTests extends ElasticsearchOperationsRoutingTests {}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.routing;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.elasticsearch.cluster.routing.Murmur3HashFunction;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class })
|
||||
public class ReactiveElasticsearchOperationsRoutingTests {
|
||||
|
||||
private static final String INDEX = "routing-test";
|
||||
private static final String ID_1 = "id1";
|
||||
private static final String ID_2 = "id2";
|
||||
private static final String ID_3 = "id3";
|
||||
|
||||
@Autowired ReactiveElasticsearchOperations operations;
|
||||
@Nullable private ReactiveIndexOperations indexOps;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
// check that the used id values go to different shards of the index which is configured to have 5 shards.
|
||||
// Elasticsearch uses the following function:
|
||||
Function<String, Integer> calcShard = routing -> Math.floorMod(Murmur3HashFunction.hash(routing), 5);
|
||||
|
||||
Integer shard1 = calcShard.apply(ID_1);
|
||||
Integer shard2 = calcShard.apply(ID_2);
|
||||
Integer shard3 = calcShard.apply(ID_3);
|
||||
|
||||
assertThat(shard1).isNotEqualTo(shard2);
|
||||
assertThat(shard1).isNotEqualTo(shard3);
|
||||
assertThat(shard2).isNotEqualTo(shard3);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexOps = operations.indexOps(RoutingEntity.class);
|
||||
indexOps.delete().then(indexOps.create()).then(indexOps.putMapping()).block();
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and be able to get it")
|
||||
void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity).then(indexOps.refresh()).block();
|
||||
|
||||
RoutingEntity savedEntity = operations.withRouting(RoutingResolver.just(ID_2)).get(entity.id, RoutingEntity.class)
|
||||
.block();
|
||||
|
||||
assertThat(savedEntity).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and be able to delete it")
|
||||
void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity).then(indexOps.refresh()).block();
|
||||
|
||||
String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, IndexCoordinates.of(INDEX))
|
||||
.block();
|
||||
|
||||
assertThat(deletedId).isEqualTo(entity.getId());
|
||||
}
|
||||
|
||||
@Test // #1218
|
||||
@DisplayName("should store data with different routing and get the routing in the search result")
|
||||
void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() {
|
||||
|
||||
RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build();
|
||||
operations.save(entity).then(indexOps.refresh()).block();
|
||||
|
||||
List<SearchHit<RoutingEntity>> searchHits = operations.search(Query.findAll(), RoutingEntity.class).collectList()
|
||||
.block();
|
||||
|
||||
assertThat(searchHits).hasSize(1);
|
||||
assertThat(searchHits.get(0).getRouting()).isEqualTo(ID_2);
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document(indexName = INDEX, shards = 5)
|
||||
@Routing("routing")
|
||||
static class RoutingEntity {
|
||||
@Id private String id;
|
||||
private String routing;
|
||||
}
|
||||
}
|
@ -43,6 +43,11 @@ public class ReactiveElasticsearchRestTemplateConfiguration extends AbstractReac
|
||||
ClientConfiguration.TerminalClientConfigurationBuilder configurationBuilder = ClientConfiguration.builder() //
|
||||
.connectedTo(elasticsearchHostPort);
|
||||
|
||||
String proxy = System.getenv("DATAES_ELASTICSEARCH_PROXY");
|
||||
|
||||
if (proxy != null) {
|
||||
configurationBuilder = configurationBuilder.withProxy(proxy);
|
||||
}
|
||||
if (clusterConnectionInfo.isUseSsl()) {
|
||||
configurationBuilder = ((ClientConfiguration.MaybeSecureClientConfigurationBuilder) configurationBuilder)
|
||||
.usingSsl();
|
||||
|
Loading…
x
Reference in New Issue
Block a user