mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 15:22:11 +00:00
Custom Order class with specific parameters for Elasticsearch.
Original Pull Request #1955 Closes #1911
This commit is contained in:
parent
7ae55b9e75
commit
b8f8a60fd7
@ -7,7 +7,8 @@ It is recommended to add those operations as custom implementation as described
|
|||||||
[[elasticsearc.misc.index.settings]]
|
[[elasticsearc.misc.index.settings]]
|
||||||
== Index settings
|
== Index settings
|
||||||
|
|
||||||
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available:
|
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation.
|
||||||
|
The following arguments are available:
|
||||||
|
|
||||||
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
|
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
|
||||||
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
|
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
|
||||||
@ -42,6 +43,7 @@ class Entity {
|
|||||||
// getter and setter...
|
// getter and setter...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
|
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
|
||||||
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
|
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
|
||||||
====
|
====
|
||||||
@ -49,13 +51,16 @@ class Entity {
|
|||||||
[[elasticsearch.misc.mappings]]
|
[[elasticsearch.misc.mappings]]
|
||||||
== Index Mapping
|
== Index Mapping
|
||||||
|
|
||||||
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties:
|
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
|
||||||
|
In addition to that it is possible to add the `@Mapping` annotation to a class.
|
||||||
|
This annotation has the following properties:
|
||||||
|
|
||||||
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
|
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
|
||||||
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
|
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
|
||||||
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
|
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
|
||||||
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
|
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
|
||||||
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
|
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
|
||||||
|
|
||||||
====
|
====
|
||||||
[source,json]
|
[source,json]
|
||||||
----
|
----
|
||||||
@ -165,7 +170,10 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
|
|||||||
[[elasticsearch.misc.sorts]]
|
[[elasticsearch.misc.sorts]]
|
||||||
== Sort options
|
== Sort options
|
||||||
|
|
||||||
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
|
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
|
||||||
|
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
|
||||||
|
|
||||||
|
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
|
||||||
|
|
||||||
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
|
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
|
||||||
|
|
||||||
|
@ -1212,6 +1212,15 @@ class RequestFactory {
|
|||||||
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
|
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
|
||||||
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
|
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
|
||||||
|
|
||||||
|
Order.Mode mode = Order.DEFAULT_MODE;
|
||||||
|
String unmappedType = null;
|
||||||
|
|
||||||
|
if (order instanceof Order) {
|
||||||
|
Order o = (Order) order;
|
||||||
|
mode = o.getMode();
|
||||||
|
unmappedType = o.getUnmappedType();
|
||||||
|
}
|
||||||
|
|
||||||
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
|
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
|
||||||
return SortBuilders //
|
return SortBuilders //
|
||||||
.scoreSort() //
|
.scoreSort() //
|
||||||
@ -1229,14 +1238,23 @@ class RequestFactory {
|
|||||||
geoDistanceOrder.getGeoPoint().getLon());
|
geoDistanceOrder.getGeoPoint().getLon());
|
||||||
|
|
||||||
sort.geoDistance(GeoDistance.fromString(geoDistanceOrder.getDistanceType().name()));
|
sort.geoDistance(GeoDistance.fromString(geoDistanceOrder.getDistanceType().name()));
|
||||||
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
|
sort.sortMode(SortMode.fromString(mode.name()));
|
||||||
sort.sortMode(SortMode.fromString(geoDistanceOrder.getMode().name()));
|
|
||||||
sort.unit(DistanceUnit.fromString(geoDistanceOrder.getUnit()));
|
sort.unit(DistanceUnit.fromString(geoDistanceOrder.getUnit()));
|
||||||
|
|
||||||
|
if (geoDistanceOrder.getIgnoreUnmapped() != GeoDistanceOrder.DEFAULT_IGNORE_UNMAPPED) {
|
||||||
|
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
|
||||||
|
}
|
||||||
|
|
||||||
return sort;
|
return sort;
|
||||||
} else {
|
} else {
|
||||||
FieldSortBuilder sort = SortBuilders //
|
FieldSortBuilder sort = SortBuilders //
|
||||||
.fieldSort(fieldName) //
|
.fieldSort(fieldName) //
|
||||||
.order(sortOrder);
|
.order(sortOrder) //
|
||||||
|
.sortMode(SortMode.fromString(mode.name()));
|
||||||
|
|
||||||
|
if (unmappedType != null) {
|
||||||
|
sort.unmappedType(unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
|
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
|
||||||
sort.missing("_first");
|
sort.missing("_first");
|
||||||
|
@ -25,16 +25,14 @@ import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
|||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class GeoDistanceOrder extends Sort.Order {
|
public class GeoDistanceOrder extends Order {
|
||||||
|
|
||||||
private static final DistanceType DEFAULT_DISTANCE_TYPE = DistanceType.arc;
|
public static final DistanceType DEFAULT_DISTANCE_TYPE = DistanceType.arc;
|
||||||
private static final Mode DEFAULT_MODE = Mode.min;
|
public static final String DEFAULT_UNIT = "m";
|
||||||
private static final String DEFAULT_UNIT = "m";
|
public static final Boolean DEFAULT_IGNORE_UNMAPPED = false;
|
||||||
private static final Boolean DEFAULT_IGNORE_UNMAPPED = false;
|
|
||||||
|
|
||||||
private final GeoPoint geoPoint;
|
private final GeoPoint geoPoint;
|
||||||
private final DistanceType distanceType;
|
private final DistanceType distanceType;
|
||||||
private final Mode mode;
|
|
||||||
private final String unit;
|
private final String unit;
|
||||||
private final Boolean ignoreUnmapped;
|
private final Boolean ignoreUnmapped;
|
||||||
|
|
||||||
@ -45,10 +43,9 @@ public class GeoDistanceOrder extends Sort.Order {
|
|||||||
|
|
||||||
private GeoDistanceOrder(String property, GeoPoint geoPoint, Sort.Direction direction, DistanceType distanceType,
|
private GeoDistanceOrder(String property, GeoPoint geoPoint, Sort.Direction direction, DistanceType distanceType,
|
||||||
Mode mode, String unit, Boolean ignoreUnmapped) {
|
Mode mode, String unit, Boolean ignoreUnmapped) {
|
||||||
super(direction, property);
|
super(direction, property, mode);
|
||||||
this.geoPoint = geoPoint;
|
this.geoPoint = geoPoint;
|
||||||
this.distanceType = distanceType;
|
this.distanceType = distanceType;
|
||||||
this.mode = mode;
|
|
||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
this.ignoreUnmapped = ignoreUnmapped;
|
this.ignoreUnmapped = ignoreUnmapped;
|
||||||
}
|
}
|
||||||
@ -119,8 +116,4 @@ public class GeoDistanceOrder extends Sort.Order {
|
|||||||
public enum DistanceType {
|
public enum DistanceType {
|
||||||
arc, plane
|
arc, plane
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Mode {
|
|
||||||
min, max, median, avg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.query;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the {@link Sort.Order} with properties that can be set on Elasticsearch order options.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public class Order extends Sort.Order {
|
||||||
|
|
||||||
|
public static final Mode DEFAULT_MODE = Mode.min;
|
||||||
|
public static final Sort.NullHandling DEFAULT_NULL_HANDLING = Sort.NullHandling.NATIVE;
|
||||||
|
|
||||||
|
protected final Mode mode;
|
||||||
|
@Nullable protected final String unmappedType;
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property) {
|
||||||
|
this(direction, property, DEFAULT_MODE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Mode mode) {
|
||||||
|
this(direction, property, DEFAULT_NULL_HANDLING, mode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, @Nullable String unmappedType) {
|
||||||
|
this(direction, property, DEFAULT_NULL_HANDLING, DEFAULT_MODE, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Mode mode, @Nullable String unmappedType) {
|
||||||
|
this(direction, property, DEFAULT_NULL_HANDLING, mode, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint) {
|
||||||
|
this(direction, property, nullHandlingHint, DEFAULT_MODE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, Mode mode) {
|
||||||
|
this(direction, property, nullHandlingHint, mode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint,
|
||||||
|
@Nullable String unmappedType) {
|
||||||
|
this(direction, property, nullHandlingHint, DEFAULT_MODE, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, Mode mode,
|
||||||
|
@Nullable String unmappedType) {
|
||||||
|
super(direction, property, nullHandlingHint);
|
||||||
|
this.mode = mode;
|
||||||
|
this.unmappedType = unmappedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getUnmappedType() {
|
||||||
|
return unmappedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort.Order with(Sort.Direction direction) {
|
||||||
|
return new Order(direction, getProperty(), getNullHandling(), mode, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort.Order withProperty(String property) {
|
||||||
|
return new Order(getDirection(), property, getNullHandling(), mode, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort.Order with(Sort.NullHandling nullHandling) {
|
||||||
|
return new Order(getDirection(), getProperty(), nullHandling, getMode(), unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order withUnmappedType(@Nullable String unmappedType) {
|
||||||
|
return new Order(getDirection(), getProperty(), getNullHandling(), getMode(), unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order with(Mode mode) {
|
||||||
|
return new Order(getDirection(), getProperty(), getNullHandling(), mode, unmappedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
min, max, median, avg
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -686,9 +686,11 @@ public abstract class ElasticsearchTemplateTests {
|
|||||||
|
|
||||||
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||||
|
|
||||||
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) //
|
||||||
.withSort(new FieldSortBuilder("rate").order(SortOrder.ASC))
|
.withSorts( //
|
||||||
.withSort(new FieldSortBuilder("message").order(SortOrder.ASC)).build();
|
new FieldSortBuilder("rate").order(SortOrder.ASC), //
|
||||||
|
new FieldSortBuilder("message").order(SortOrder.ASC)) //
|
||||||
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
||||||
@ -725,8 +727,8 @@ public abstract class ElasticsearchTemplateTests {
|
|||||||
|
|
||||||
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||||
|
|
||||||
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
Query searchQuery = operations.matchAllQuery();
|
||||||
.withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsFirst()))).build();
|
searchQuery.setPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsFirst())));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
||||||
@ -763,8 +765,8 @@ public abstract class ElasticsearchTemplateTests {
|
|||||||
|
|
||||||
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||||
|
|
||||||
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
Query searchQuery = operations.matchAllQuery();
|
||||||
.withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsLast()))).build();
|
searchQuery.setPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsLast())));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
|
||||||
@ -1139,8 +1141,8 @@ public abstract class ElasticsearchTemplateTests {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
|
|
||||||
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
Query searchQuery = operations.matchAllQuery();
|
||||||
.withPageable(PageRequest.of(0, 10)).build();
|
searchQuery.setPageable(PageRequest.of(0, 10));
|
||||||
|
|
||||||
SearchScrollHits<SampleEntity> scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000,
|
SearchScrollHits<SampleEntity> scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000,
|
||||||
searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName()));
|
searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||||
@ -3591,6 +3593,29 @@ public abstract class ElasticsearchTemplateTests {
|
|||||||
operations.index(query, IndexCoordinates.of(indexNameProvider.indexName()));
|
operations.index(query, IndexCoordinates.of(indexNameProvider.indexName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #1945
|
||||||
|
@DisplayName("should error on sort with unmapped field and default settings")
|
||||||
|
void shouldErrorOnSortWithUnmappedFieldAndDefaultSettings() {
|
||||||
|
|
||||||
|
Sort.Order order = new Sort.Order(Sort.Direction.ASC, "unmappedField");
|
||||||
|
Query query = operations.matchAllQuery().addSort(Sort.by(order));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> {
|
||||||
|
operations.search(query, SampleEntity.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #1945
|
||||||
|
@DisplayName("should not error on sort with unmapped field and unmapped_type settings")
|
||||||
|
void shouldNotErrorOnSortWithUnmappedFieldAndUnmappedTypeSettings() {
|
||||||
|
|
||||||
|
org.springframework.data.elasticsearch.core.query.Order order = new org.springframework.data.elasticsearch.core.query.Order(
|
||||||
|
Sort.Direction.ASC, "unmappedField").withUnmappedType("long");
|
||||||
|
Query query = operations.matchAllQuery().addSort(Sort.by(order));
|
||||||
|
|
||||||
|
operations.search(query, SampleEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
// region entities
|
// region entities
|
||||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||||
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")
|
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user