diff --git a/src/main/asciidoc/reference/elasticsearch-repositories.adoc b/src/main/asciidoc/reference/elasticsearch-repositories.adoc index c0b770538..7ac087840 100644 --- a/src/main/asciidoc/reference/elasticsearch-repositories.adoc +++ b/src/main/asciidoc/reference/elasticsearch-repositories.adoc @@ -53,7 +53,7 @@ interface BookRepository extends Repository { @HighlightField(name = "name"), @HighlightField(name = "summary") }) - List> findByNameOrSummary(String text, String summary); + SearchHits findByNameOrSummary(String text, String summary); } ---- ==== @@ -62,6 +62,30 @@ It is possible to define multiple fields to be highlighted like above, and both In the search results the highlight data can be retrieved from the `SearchHit` class. +=== @SourceFilters + +Sometimes the user does not need to have all the properties of an entity returned from a search but only a subset. +Elasticsearch provides source filtering to reduce the amount of data that is transferred across the network to the +application. + +When working with `Query` implementations and the `ElasticsearchOperations` this is easily possible by setting a +source filter on the query. + +When using repository methods there is the `@SourceFilters` annotation: + +==== +[source,java] +---- +interface BookRepository extends Repository { + + @SourceFilters(includes = "name") + SearchHits findByName(String text); +} +---- +==== + +In this example, all the properties of the returned `Book` objects would be `null` except the name. + [[elasticsearch.annotation]] == Annotation based configuration diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java index eca6b189f..41fc58399 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java @@ -16,11 +16,11 @@ package org.springframework.data.elasticsearch.annotations; /** - * Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken - * from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the - * same values as the Elasticsearch formatter. Use format = {} to disable built-in date * formats in - * the @Field annotation. If you want to use only a custom date format pattern, you must set the format * - * property to empty {}. + * Values based on Elasticsearch reference + * documentation. The patterns are taken from this documentation and slightly adapted so that a Java + * {@link java.time.format.DateTimeFormatter} produces the same values as the Elasticsearch formatter. Use + * format = {} to disable built-in date formats in the {@link Field} annotation. If you want to use only a + * custom date format pattern, you must set the format property to empty {}. * * @author Jakub Vavrik * @author Tim te Beek @@ -49,7 +49,7 @@ public enum DateFormat { date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), // date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), // date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), // - date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation + date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation epoch_millis("epoch_millis"), // epoch_second("epoch_second"), // hour("HH"), // diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index 80d967cc7..e83e42bf0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -53,9 +53,8 @@ public @interface Field { String value() default ""; /** - * The name to be used to store the field inside the document. - *

- * √5 If not set, the name of the annotated property is used. + * The name to be used to store the field inside the document. If not set, the name of the annotated property + * is used. * * @since 3.2 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index 74c8bd6e0..b40516bc5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -1031,6 +1031,7 @@ class RequestConverter { Assert.notNull(query, "query must not be null"); Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + elasticsearchConverter.updateQuery(query, clazz); SearchRequest.Builder builder = new SearchRequest.Builder(); prepareSearchRequest(query, clazz, indexCoordinates, builder, forCount, useScroll); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java b/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java index fda41dbf3..f9feceabb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java @@ -15,19 +15,29 @@ */ package org.springframework.data.elasticsearch.core.query; +import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.lang.Nullable; /** - * SourceFilter for providing includes and excludes. + * SourceFilter for providing includes and excludes. Using these helps in reducing the amount of data that is returned + * from Elasticsearch especially when the stored docuements are large and only some fields from these documents are + * needed. If the SourceFilter includes the name of a property that has a different name mapped in Elasticsearch (see + * {@link Field#name()} this will automatically be mapped. * * @author Jon Tsiros * @author Peter-Josef Meisch */ public interface SourceFilter { + /** + * @return the name of the fields to include in a response. + */ @Nullable String[] getIncludes(); + /** + * @return the names of the fields to exclude from a response. + */ @Nullable String[] getExcludes(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java index 635c194ba..e983d65de 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractElasticsearchRepositoryQuery.java @@ -57,7 +57,6 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository protected void prepareQuery(Query query, Class clazz, ParameterAccessor parameterAccessor) { - elasticsearchConverter.updateQuery(query, clazz); if (queryMethod.hasAnnotatedHighlight()) { query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery()); @@ -68,5 +67,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository if (sourceFilter != null) { query.addSourceFilter(sourceFilter); } + + elasticsearchConverter.updateQuery(query, clazz); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java index a70c4b23b..f5268ecb2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java @@ -131,6 +131,9 @@ public class ElasticsearchQueryMethod extends QueryMethod { } private HighlightQuery createAnnotatedHighlightQuery() { + + Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null"); + return new HighlightQuery( org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation), getDomainClass()); @@ -140,6 +143,7 @@ public class ElasticsearchQueryMethod extends QueryMethod { * @return the {@link ElasticsearchEntityMetadata} for the query methods {@link #getReturnedObjectType() return type}. * @since 3.2 */ + @SuppressWarnings("unchecked") @Override public ElasticsearchEntityMetadata getEntityInformation() { @@ -260,23 +264,6 @@ public class ElasticsearchQueryMethod extends QueryMethod { return queryAnnotation != null && queryAnnotation.count(); } - /** - * @return {@literal true} if the method is annotated with {@link SourceFilters}. - * @since 5.0 - */ - public boolean hasSourceFilters() { - return sourceFilters != null; - } - - /** - * @return the {@link SourceFilters} annotation for this method. - * @since 5.0 - */ - @Nullable - public SourceFilters getSourceFilters() { - return sourceFilters; - } - /** * Uses the sourceFilters property to create a {@link SourceFilter} to be added to a * {@link org.springframework.data.elasticsearch.core.query.Query} @@ -295,29 +282,25 @@ public class ElasticsearchQueryMethod extends QueryMethod { return null; } - ElasticsearchPersistentEntity persistentEntity = converter.getMappingContext() - .getPersistentEntity(getEntityInformation().getJavaType()); - StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService()); FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder(); if (sourceFilters.includes().length > 0) { fetchSourceFilterBuilder - .withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil, persistentEntity)); + .withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil)); } if (sourceFilters.excludes().length > 0) { fetchSourceFilterBuilder - .withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil, persistentEntity)); + .withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil)); } return fetchSourceFilterBuilder.build(); } - private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil, - @Nullable ElasticsearchPersistentEntity persistentEntity) { + private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil) { - List unmappedFieldNames = new ArrayList<>(); + List fieldNames = new ArrayList<>(); for (String s : source) { @@ -325,21 +308,17 @@ public class ElasticsearchQueryMethod extends QueryMethod { String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor); // this could be "[\"foo\",\"bar\"]", must be split if (fieldName.startsWith("[") && fieldName.endsWith("]")) { - unmappedFieldNames.addAll( // + //noinspection RegExpRedundantEscape + fieldNames.addAll( // Arrays.asList(fieldName.substring(1, fieldName.length() - 2) // .replaceAll("\\\"", "") // .split(","))); // } else { - unmappedFieldNames.add(fieldName); + fieldNames.add(fieldName); } } } - return unmappedFieldNames.stream().map(fieldName -> { - ElasticsearchPersistentProperty property = persistentEntity != null - ? persistentEntity.getPersistentProperty(fieldName) - : null; - return property != null ? property.getFieldName() : fieldName; - }).toArray(String[]::new); + return fieldNames.toArray(new String[0]); } }