sourcefilter documentation.

Original Pull Request #2258
Closes #2257
This commit is contained in:
Peter-Josef Meisch 2022-08-07 12:22:46 +02:00 committed by GitHub
parent 44a79093ce
commit df5fd0b97c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 59 additions and 45 deletions

View File

@ -53,7 +53,7 @@ interface BookRepository extends Repository<Book, String> {
@HighlightField(name = "name"), @HighlightField(name = "name"),
@HighlightField(name = "summary") @HighlightField(name = "summary")
}) })
List<SearchHit<Book>> findByNameOrSummary(String text, String summary); SearchHits<Book> 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. 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<Book, String> {
@SourceFilters(includes = "name")
SearchHits<Book> findByName(String text);
}
----
====
In this example, all the properties of the returned `Book` objects would be `null` except the name.
[[elasticsearch.annotation]] [[elasticsearch.annotation]]
== Annotation based configuration == Annotation based configuration

View File

@ -16,11 +16,11 @@
package org.springframework.data.elasticsearch.annotations; package org.springframework.data.elasticsearch.annotations;
/** /**
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken * Values based on <a href="https://www.elastic.co/guide/reference/mapping/date-format/">Elasticsearch reference
* from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the * documentation</a>. The patterns are taken from this documentation and slightly adapted so that a Java
* same values as the Elasticsearch formatter. Use <code>format = {}</code> to disable built-in date * formats in * {@link java.time.format.DateTimeFormatter} produces the same values as the Elasticsearch formatter. Use
* the @Field annotation. If you want to use only a custom date format pattern, you must set the <code>format</code> * * <code>format = {}</code> to disable built-in date formats in the {@link Field} annotation. If you want to use only a
* property to empty <code>{}</code>. * custom date format pattern, you must set the <code>format</code> property to empty <code>{}</code>.
* *
* @author Jakub Vavrik * @author Jakub Vavrik
* @author Tim te Beek * @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_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), // date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
date_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_millis("epoch_millis"), //
epoch_second("epoch_second"), // epoch_second("epoch_second"), //
hour("HH"), // hour("HH"), //

View File

@ -53,9 +53,8 @@ public @interface Field {
String value() default ""; String value() default "";
/** /**
* The <em>name</em> to be used to store the field inside the document. * The <em>name</em> to be used to store the field inside the document. If not set, the name of the annotated property
* <p> * is used.
* 5 If not set, the name of the annotated property is used.
* *
* @since 3.2 * @since 3.2
*/ */

View File

@ -1031,6 +1031,7 @@ class RequestConverter {
Assert.notNull(query, "query must not be null"); Assert.notNull(query, "query must not be null");
Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
elasticsearchConverter.updateQuery(query, clazz);
SearchRequest.Builder builder = new SearchRequest.Builder(); SearchRequest.Builder builder = new SearchRequest.Builder();
prepareSearchRequest(query, clazz, indexCoordinates, builder, forCount, useScroll); prepareSearchRequest(query, clazz, indexCoordinates, builder, forCount, useScroll);

View File

@ -15,19 +15,29 @@
*/ */
package org.springframework.data.elasticsearch.core.query; package org.springframework.data.elasticsearch.core.query;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.lang.Nullable; 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 Jon Tsiros
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
*/ */
public interface SourceFilter { public interface SourceFilter {
/**
* @return the name of the fields to include in a response.
*/
@Nullable @Nullable
String[] getIncludes(); String[] getIncludes();
/**
* @return the names of the fields to exclude from a response.
*/
@Nullable @Nullable
String[] getExcludes(); String[] getExcludes();
} }

View File

@ -57,7 +57,6 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
protected void prepareQuery(Query query, Class<?> clazz, ParameterAccessor parameterAccessor) { protected void prepareQuery(Query query, Class<?> clazz, ParameterAccessor parameterAccessor) {
elasticsearchConverter.updateQuery(query, clazz);
if (queryMethod.hasAnnotatedHighlight()) { if (queryMethod.hasAnnotatedHighlight()) {
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery()); query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
@ -68,5 +67,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
if (sourceFilter != null) { if (sourceFilter != null) {
query.addSourceFilter(sourceFilter); query.addSourceFilter(sourceFilter);
} }
elasticsearchConverter.updateQuery(query, clazz);
} }
} }

View File

@ -131,6 +131,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
} }
private HighlightQuery createAnnotatedHighlightQuery() { private HighlightQuery createAnnotatedHighlightQuery() {
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
return new HighlightQuery( return new HighlightQuery(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation), org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
getDomainClass()); getDomainClass());
@ -140,6 +143,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
* @return the {@link ElasticsearchEntityMetadata} for the query methods {@link #getReturnedObjectType() return type}. * @return the {@link ElasticsearchEntityMetadata} for the query methods {@link #getReturnedObjectType() return type}.
* @since 3.2 * @since 3.2
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public ElasticsearchEntityMetadata<?> getEntityInformation() { public ElasticsearchEntityMetadata<?> getEntityInformation() {
@ -260,23 +264,6 @@ public class ElasticsearchQueryMethod extends QueryMethod {
return queryAnnotation != null && queryAnnotation.count(); 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 * Uses the sourceFilters property to create a {@link SourceFilter} to be added to a
* {@link org.springframework.data.elasticsearch.core.query.Query} * {@link org.springframework.data.elasticsearch.core.query.Query}
@ -295,29 +282,25 @@ public class ElasticsearchQueryMethod extends QueryMethod {
return null; return null;
} }
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(getEntityInformation().getJavaType());
StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService()); StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder(); FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
if (sourceFilters.includes().length > 0) { if (sourceFilters.includes().length > 0) {
fetchSourceFilterBuilder fetchSourceFilterBuilder
.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil, persistentEntity)); .withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil));
} }
if (sourceFilters.excludes().length > 0) { if (sourceFilters.excludes().length > 0) {
fetchSourceFilterBuilder fetchSourceFilterBuilder
.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil, persistentEntity)); .withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil));
} }
return fetchSourceFilterBuilder.build(); return fetchSourceFilterBuilder.build();
} }
private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil, private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor, StringQueryUtil stringQueryUtil) {
@Nullable ElasticsearchPersistentEntity<?> persistentEntity) {
List<String> unmappedFieldNames = new ArrayList<>(); List<String> fieldNames = new ArrayList<>();
for (String s : source) { for (String s : source) {
@ -325,21 +308,17 @@ public class ElasticsearchQueryMethod extends QueryMethod {
String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor); String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
// this could be "[\"foo\",\"bar\"]", must be split // this could be "[\"foo\",\"bar\"]", must be split
if (fieldName.startsWith("[") && fieldName.endsWith("]")) { if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
unmappedFieldNames.addAll( // //noinspection RegExpRedundantEscape
fieldNames.addAll( //
Arrays.asList(fieldName.substring(1, fieldName.length() - 2) // Arrays.asList(fieldName.substring(1, fieldName.length() - 2) //
.replaceAll("\\\"", "") // .replaceAll("\\\"", "") //
.split(","))); // .split(","))); //
} else { } else {
unmappedFieldNames.add(fieldName); fieldNames.add(fieldName);
} }
} }
} }
return unmappedFieldNames.stream().map(fieldName -> { return fieldNames.toArray(new String[0]);
ElasticsearchPersistentProperty property = persistentEntity != null
? persistentEntity.getPersistentProperty(fieldName)
: null;
return property != null ? property.getFieldName() : fieldName;
}).toArray(String[]::new);
} }
} }