mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-07-12 13:23:26 +00:00
Add repository method support for search templates.
Original Pull Request #3049 Closes #2997 Signed-off-by: Peter-Josef Meisch <pj.meisch@sothawo.com>
This commit is contained in:
parent
5568c7bbc4
commit
cb77b328ae
@ -3,7 +3,9 @@
|
||||
|
||||
[[new-features.5-5-0]]
|
||||
== New in Spring Data Elasticsearch 5.5
|
||||
|
||||
* Upgrade to Elasticsearch 8.17.0.
|
||||
* Add support for the `@SearchTemplateQuery` annotation on repository methods.
|
||||
|
||||
[[new-features.5-4-0]]
|
||||
== New in Spring Data Elasticsearch 5.4
|
||||
|
@ -365,6 +365,8 @@ operations.putScript( <.>
|
||||
|
||||
To use a search template in a search query, Spring Data Elasticsearch provides the `SearchTemplateQuery`, an implementation of the `org.springframework.data.elasticsearch.core.query.Query` interface.
|
||||
|
||||
NOTE: Although `SearchTemplateQuery` is an implementation of the `Query` interface, not all of the functionality provided by the base class is available for a `SearchTemplateQuery` like setting a `Pageable` or a `Sort`. Values for this functionality must be added to the stored script like shown in the following example for paging parameters. If these values are set on the `Query` object, they will be ignored.
|
||||
|
||||
In the following code, we will add a call using a search template query to a custom repository implementation (see
|
||||
xref:repositories/custom-implementations.adoc[]) as an example how this can be integrated into a repository call.
|
||||
|
||||
@ -449,4 +451,3 @@ var query = Query.findAll().addSort(Sort.by(order));
|
||||
About the filter query: It is not possible to use a `CriteriaQuery` here, as this query would be converted into a Elasticsearch nested query which does not work in the filter context. So only `StringQuery` or `NativeQuery` can be used here. When using one of these, like the term query above, the Elasticsearch field names must be used, so take care, when these are redefined with the `@Field(name="...")` definition.
|
||||
|
||||
For the definition of the order path and the nested paths, the Java entity property names should be used.
|
||||
|
||||
|
@ -10,7 +10,9 @@ The Elasticsearch module supports all basic query building feature as string que
|
||||
=== Declared queries
|
||||
|
||||
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names.
|
||||
In this case one might make use of the `@Query` annotation (see xref:elasticsearch/repositories/elasticsearch-repository-queries.adoc#elasticsearch.query-methods.at-query[Using @Query Annotation] ).
|
||||
In this case one might make use of the `@Query` annotation (see xref:elasticsearch/repositories/elasticsearch-repository-queries.adoc#elasticsearch.query-methods.at-query[Using the @Query Annotation] ).
|
||||
|
||||
Another possibility is the use of a search-template, (see xref:elasticsearch/repositories/elasticsearch-repository-queries.adoc#elasticsearch.query-methods.at-searchtemplate-query[Using the @SearchTemplateQuery Annotation] ).
|
||||
|
||||
[[elasticsearch.query-methods.criterions]]
|
||||
== Query creation
|
||||
@ -312,11 +314,13 @@ Repository methods can be defined to have the following return types for returni
|
||||
* `SearchPage<T>`
|
||||
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
== Using @Query Annotation
|
||||
== Using the @Query Annotation
|
||||
|
||||
.Declare query on the method using the `@Query` annotation.
|
||||
====
|
||||
The arguments passed to the method can be inserted into placeholders in the query string. The placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
|
||||
The arguments passed to the method can be inserted into placeholders in the query string.
|
||||
The placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@ -341,15 +345,20 @@ It will be sent to Easticsearch as value of the query element; if for example th
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
.`@Query` annotation on a method taking a Collection argument
|
||||
====
|
||||
A repository method such as
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Query("{\"ids\": {\"values\": ?0 }}")
|
||||
List<SampleEntity> getByIds(Collection<String> ids);
|
||||
----
|
||||
would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html[IDs query] to return all the matching documents. So calling the method with a `List` of `["id1", "id2", "id3"]` would produce the query body
|
||||
|
||||
would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html[IDs query] to return all the matching documents.
|
||||
So calling the method with a `List` of `["id1", "id2", "id3"]` would produce the query body
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@ -369,7 +378,6 @@ would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/qu
|
||||
====
|
||||
https://docs.spring.io/spring-framework/reference/core/expressions.html[SpEL expression] is also supported when defining query in `@Query`.
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@ -411,6 +419,7 @@ If for example the function is called with the parameter _John_, it would produc
|
||||
.accessing parameter property.
|
||||
====
|
||||
Supposing that we have the following class as query parameter type:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public record QueryParameter(String value) {
|
||||
@ -444,7 +453,9 @@ We can pass `new QueryParameter("John")` as the parameter now, and it will produ
|
||||
|
||||
.accessing bean property.
|
||||
====
|
||||
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/bean-references.html[Bean property] is also supported to access. Given that there is a bean named `queryParameter` of type `QueryParameter`, we can access the bean with symbol `@` rather than `#`, and there is no need to declare a parameter of type `QueryParameter` in the query method:
|
||||
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/bean-references.html[Bean property] is also supported to access.
|
||||
Given that there is a bean named `queryParameter` of type `QueryParameter`, we can access the bean with symbol `@` rather than `#`, and there is no need to declare a parameter of type `QueryParameter` in the query method:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@ -493,6 +504,7 @@ interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
NOTE: collection values should not be quoted when declaring the elasticsearch json query.
|
||||
|
||||
A collection of `names` like `List.of("name1", "name2")` will produce the following terms query:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
@ -532,6 +544,7 @@ interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
Page<Book> findByName(Collection<QueryParameter> parameters, Pageable pageable);
|
||||
}
|
||||
----
|
||||
|
||||
This will extract all the `value` property values as a new `Collection` from `QueryParameter` collection, thus takes the same effect as above.
|
||||
====
|
||||
|
||||
@ -560,3 +573,20 @@ interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
----
|
||||
|
||||
====
|
||||
|
||||
[[elasticsearch.query-methods.at-searchtemplate-query]]
|
||||
== Using the @SearchTemplateQuery Annotation
|
||||
|
||||
When using Elasticsearch search templates - (see xref:elasticsearch/misc.adoc#elasticsearch.misc.searchtemplates [Search Template support]) it is possible to specify that a repository method should use a template by adding the `@SearchTemplateQuery` annotation to that method.
|
||||
|
||||
Let's assume that there is a search template stored with the name "book-by-title" and this template need a parameter named "title", then a repository method using that search template can be defined like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@SearchTemplateQuery(id = "book-by-title")
|
||||
SearchHits<Book> findByTitle(String title);
|
||||
}
|
||||
----
|
||||
|
||||
The parameters of the repository method are sent to the seacrh template as key/value pairs where the key is the parameter name and the value is taken from the actual value when the method is invoked.
|
||||
|
@ -9,4 +9,14 @@ This section describes breaking changes from version 5.4.x to 5.5.x and how remo
|
||||
[[elasticsearch-migration-guide-5.4-5.5.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
Some classes that probably are not used by a library user have been renamed, the classes with the old names are still there, but are deprecated:
|
||||
|
||||
|===
|
||||
|old name|new name
|
||||
|
||||
|ElasticsearchPartQuery|RepositoryPartQuery
|
||||
|ElasticsearchStringQuery|RepositoryStringQuery
|
||||
|ReactiveElasticsearchStringQuery|ReactiveRepositoryStringQuery
|
||||
|===
|
||||
|
||||
=== Removals
|
||||
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 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 org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a repository method as a search template method. The annotation defines the search template id,
|
||||
* the parameters for the search template are taken from the method's arguments.
|
||||
*
|
||||
* @author P.J. Meisch (pj.meisch@sothawo.com)
|
||||
* @since 5.5
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Documented
|
||||
@QueryAnnotation
|
||||
public @interface SearchTemplateQuery {
|
||||
/**
|
||||
* The id of the search template. Must not be empt or null.
|
||||
*/
|
||||
String id();
|
||||
}
|
@ -15,22 +15,22 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class SearchTemplateQueryBuilder extends BaseQueryBuilder<SearchTemplateQuery, SearchTemplateQueryBuilder> {
|
||||
|
||||
@Nullable
|
||||
private String id;
|
||||
@Nullable private String id;
|
||||
@Nullable String source;
|
||||
|
||||
@Nullable
|
||||
Map<String, Object> params;
|
||||
@Nullable Map<String, Object> params;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
@ -62,6 +62,18 @@ public class SearchTemplateQueryBuilder extends BaseQueryBuilder<SearchTemplateQ
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchTemplateQueryBuilder withSort(Sort sort) {
|
||||
throw new IllegalArgumentException(
|
||||
"sort is not supported in a searchtemplate query. Sort values must be defined in the stored template");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchTemplateQueryBuilder withPageable(Pageable pageable) {
|
||||
throw new IllegalArgumentException(
|
||||
"paging is not supported in a searchtemplate query. from and size values must be defined in the stored template");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchTemplateQuery build() {
|
||||
return new SearchTemplateQuery(this);
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
@ -114,12 +115,16 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
|
||||
} else if (queryMethod.isCollectionQuery()) {
|
||||
if (query instanceof SearchTemplateQuery) {
|
||||
// we cannot get a count here, from and size would be in the template
|
||||
} else {
|
||||
if (parameterAccessor.getPageable().isUnpaged()) {
|
||||
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
|
||||
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
|
||||
} else {
|
||||
query.setPageable(parameterAccessor.getPageable());
|
||||
}
|
||||
}
|
||||
result = elasticsearchOperations.search(query, clazz, index);
|
||||
} else {
|
||||
result = elasticsearchOperations.searchOne(query, clazz, index);
|
||||
@ -137,7 +142,8 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
|
||||
queryMethod.addSpecialMethodParameters(query, parameterAccessor,
|
||||
elasticsearchOperations.getElasticsearchConverter(),
|
||||
evaluationContextProvider);
|
||||
|
||||
return query;
|
||||
|
@ -105,7 +105,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
var query = createQuery(parameterAccessor);
|
||||
Assert.notNull(query, "unsupported query");
|
||||
|
||||
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
|
||||
queryMethod.addSpecialMethodParameters(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
|
||||
evaluationContextProvider);
|
||||
|
||||
String indexName = queryMethod.getEntityInformation().getIndexName();
|
||||
|
@ -33,42 +33,11 @@ import org.springframework.data.repository.query.parser.PartTree;
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @deprecated since 5.5, use {@link RepositoryPartQuery} instead
|
||||
*/
|
||||
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private final PartTree tree;
|
||||
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@Deprecated(forRemoval = true)
|
||||
public class ElasticsearchPartQuery extends RepositoryPartQuery {
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(method, elasticsearchOperations, evaluationContextProvider);
|
||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
||||
this.mappingContext = elasticsearchConverter.getMappingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return tree.isCountProjection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return tree.isDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return tree.isExistsProjection();
|
||||
}
|
||||
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor) {
|
||||
|
||||
BaseQuery query = new ElasticsearchQueryCreator(tree, accessor, mappingContext).createQuery();
|
||||
|
||||
if (tree.getMaxResults() != null) {
|
||||
query.setMaxResults(tree.getMaxResults());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.annotations.Highlight;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.annotations.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.annotations.SourceFilters;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
@ -84,6 +85,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
@Nullable private final Query queryAnnotation;
|
||||
@Nullable private final Highlight highlightAnnotation;
|
||||
@Nullable private final SourceFilters sourceFilters;
|
||||
@Nullable private final SearchTemplateQuery searchTemplateQueryAnnotation;
|
||||
|
||||
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
@ -98,6 +100,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
this.highlightAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Highlight.class);
|
||||
this.sourceFilters = AnnotatedElementUtils.findMergedAnnotation(method, SourceFilters.class);
|
||||
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(repositoryMetadata, method);
|
||||
this.searchTemplateQueryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, SearchTemplateQuery.class);
|
||||
|
||||
verifyCountQueryTypes();
|
||||
}
|
||||
@ -125,12 +128,16 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the method is annotated with the {@link Query} annotation.
|
||||
*/
|
||||
public boolean hasAnnotatedQuery() {
|
||||
return this.queryAnnotation != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the query String. Must not be {@literal null} when {@link #hasAnnotatedQuery()} returns true
|
||||
* @return the query String defined in the {@link Query} annotation. Must not be {@literal null} when
|
||||
* {@link #hasAnnotatedQuery()} returns true.
|
||||
*/
|
||||
@Nullable
|
||||
public String getAnnotatedQuery() {
|
||||
@ -158,6 +165,27 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
return new HighlightQuery(highlightConverter.convert(highlightAnnotation), getDomainClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the method is annotated with the {@link SearchTemplateQuery} annotation.
|
||||
* @since 5.5
|
||||
*/
|
||||
public boolean hasAnnotatedSearchTemplateQuery() {
|
||||
return this.searchTemplateQueryAnnotation != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SearchTemplateQuery} annotation
|
||||
* @throws IllegalArgumentException if no {@link SearchTemplateQuery} annotation is present on the method
|
||||
* @since 5.5
|
||||
*/
|
||||
public SearchTemplateQuery getAnnotatedSearchTemplateQuery() {
|
||||
|
||||
Assert.isTrue(hasAnnotatedSearchTemplateQuery(), "no SearchTemplateQuery annotation present on " + getName());
|
||||
Assert.notNull(searchTemplateQueryAnnotation, "highlsearchTemplateQueryAnnotationightAnnotation must not be null");
|
||||
|
||||
return searchTemplateQueryAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ElasticsearchEntityMetadata} for the query methods {@link #getReturnedObjectType() return type}.
|
||||
* @since 3.2
|
||||
@ -377,7 +405,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
}
|
||||
}
|
||||
|
||||
void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
void addSpecialMethodParameters(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
|
||||
ElasticsearchConverter elasticsearchConverter,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
|
@ -15,13 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* ElasticsearchStringQuery
|
||||
@ -32,43 +27,12 @@ import org.springframework.util.Assert;
|
||||
* @author Taylor Ono
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @deprecated since 5.5, use {@link RepositoryStringQuery}
|
||||
*/
|
||||
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private final String queryString;
|
||||
|
||||
@Deprecated(since = "5.5", forRemoval = true)
|
||||
public class ElasticsearchStringQuery extends RepositoryStringQuery {
|
||||
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||
String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(queryString, "Query cannot be empty");
|
||||
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
|
||||
|
||||
this.queryString = queryString;
|
||||
super(queryMethod, elasticsearchOperations, queryString, evaluationContextProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return queryMethod.hasCountQueryAnnotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
|
||||
var processed = new QueryStringProcessor(queryString, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
|
||||
return new StringQuery(processed)
|
||||
.addSort(parameterAccessor.getSort());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,68 +15,26 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Taylor Ono
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
* @deprecated since 5.5, use {@link ReactiveRepositoryStringQuery}
|
||||
*/
|
||||
public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private final String query;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
@Deprecated(since = "5.5", forRemoval = true)
|
||||
public class ReactiveElasticsearchStringQuery extends ReactiveRepositoryStringQuery {
|
||||
|
||||
public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
this(queryMethod.getAnnotatedQuery(), queryMethod, operations, evaluationContextProvider);
|
||||
super(queryMethod, operations, evaluationContextProvider);
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, operations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
|
||||
this.query = query;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = getElasticsearchOperations().getElasticsearchConverter()
|
||||
.getConversionService();
|
||||
String processed = new QueryStringProcessor(query, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
return new StringQuery(processed);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCountQuery() {
|
||||
return queryMethod.hasCountQueryAnnotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLimiting() {
|
||||
return false;
|
||||
super(query, queryMethod, operations, evaluationContextProvider);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A reactive repository query that uses a search template already stored in Elasticsearch.
|
||||
*
|
||||
* @author P.J. Meisch (pj.meisch@sothawo.com)
|
||||
* @since 5.5
|
||||
*/
|
||||
public class ReactiveRepositorySearchTemplateQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private String id;
|
||||
private Map<String, Object> params;
|
||||
|
||||
public ReactiveRepositorySearchTemplateQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations elasticsearchOperations, QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
String id) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
Assert.hasLength(id, "id must not be null or empty");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLimiting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
|
||||
var searchTemplateParameters = new LinkedHashMap<String, Object>();
|
||||
var values = parameterAccessor.getValues();
|
||||
|
||||
parameterAccessor.getParameters().forEach(parameter -> {
|
||||
if (!parameter.isSpecialParameter() && parameter.getName().isPresent() && parameter.getIndex() <= values.length) {
|
||||
searchTemplateParameters.put(parameter.getName().get(), values[parameter.getIndex()]);
|
||||
}
|
||||
});
|
||||
|
||||
return SearchTemplateQuery.builder()
|
||||
.withId(id)
|
||||
.withParams(searchTemplateParameters)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2019-2024 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.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Was originally named ReactiveElasticsearchStringQuery.
|
||||
* @author Christoph Strobl
|
||||
* @author Taylor Ono
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveRepositoryStringQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private final String query;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public ReactiveRepositoryStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
this(queryMethod.getAnnotatedQuery(), queryMethod, operations, evaluationContextProvider);
|
||||
}
|
||||
|
||||
public ReactiveRepositoryStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, operations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
|
||||
|
||||
this.query = query;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = getElasticsearchOperations().getElasticsearchConverter()
|
||||
.getConversionService();
|
||||
String processed = new QueryStringProcessor(query, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
return new StringQuery(processed);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCountQuery() {
|
||||
return queryMethod.hasCountQueryAnnotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLimiting() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2024 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.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
* A repository query that is built from the the method name in the repository definition.
|
||||
* Was originally named ElasticsearchPartQuery.
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Kevin Leturc
|
||||
* @author Mark Paluch
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class RepositoryPartQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private final PartTree tree;
|
||||
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
public RepositoryPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(method, elasticsearchOperations, evaluationContextProvider);
|
||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
||||
this.mappingContext = elasticsearchConverter.getMappingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return tree.isCountProjection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return tree.isDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return tree.isExistsProjection();
|
||||
}
|
||||
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor) {
|
||||
|
||||
BaseQuery query = new ElasticsearchQueryCreator(tree, accessor, mappingContext).createQuery();
|
||||
|
||||
if (tree.getMaxResults() != null) {
|
||||
query.setMaxResults(tree.getMaxResults());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A repository query that uses a search template already stored in Elasticsearch.
|
||||
*
|
||||
* @author P.J. Meisch (pj.meisch@sothawo.com)
|
||||
* @since 5.5
|
||||
*/
|
||||
public class RepositorySearchTemplateQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private String id;
|
||||
private Map<String, Object> params;
|
||||
|
||||
public RepositorySearchTemplateQuery(ElasticsearchQueryMethod queryMethod,
|
||||
ElasticsearchOperations elasticsearchOperations, QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
String id) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
Assert.hasLength(id, "id must not be null or empty");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
|
||||
var searchTemplateParameters = new LinkedHashMap<String, Object>();
|
||||
var values = parameterAccessor.getValues();
|
||||
|
||||
parameterAccessor.getParameters().forEach(parameter -> {
|
||||
if (!parameter.isSpecialParameter() && parameter.getName().isPresent() && parameter.getIndex() <= values.length) {
|
||||
searchTemplateParameters.put(parameter.getName().get(), values[parameter.getIndex()]);
|
||||
}
|
||||
});
|
||||
|
||||
return SearchTemplateQuery.builder()
|
||||
.withId(id)
|
||||
.withParams(searchTemplateParameters)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A repository query that is defined by a String containing the query.
|
||||
* Was originally named ElasticsearchStringQuery.
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Mark Paluch
|
||||
* @author Taylor Ono
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
public class RepositoryStringQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
private final String queryString;
|
||||
|
||||
public RepositoryStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||
String queryString, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(queryString, "Query cannot be empty");
|
||||
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
|
||||
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountQuery() {
|
||||
return queryMethod.hasCountQueryAnnotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDeleteQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistsQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected BaseQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor) {
|
||||
ConversionService conversionService = elasticsearchOperations.getElasticsearchConverter().getConversionService();
|
||||
var processed = new QueryStringProcessor(queryString, queryMethod, conversionService, evaluationContextProvider)
|
||||
.createQuery(parameterAccessor);
|
||||
|
||||
return new StringQuery(processed)
|
||||
.addSort(parameterAccessor.getSort());
|
||||
}
|
||||
}
|
@ -22,9 +22,10 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.RepositoryPartQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.RepositorySearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.RepositoryStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.querybyexample.QueryByExampleElasticsearchExecutor;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
@ -122,13 +123,17 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, namedQuery,
|
||||
return new RepositoryStringQuery(queryMethod, elasticsearchOperations, namedQuery,
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new ElasticsearchStringQuery(queryMethod, elasticsearchOperations, queryMethod.getAnnotatedQuery(),
|
||||
return new RepositoryStringQuery(queryMethod, elasticsearchOperations, queryMethod.getAnnotatedQuery(),
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedSearchTemplateQuery()) {
|
||||
var searchTemplateQuery = queryMethod.getAnnotatedSearchTemplateQuery();
|
||||
return new RepositorySearchTemplateQuery(queryMethod, elasticsearchOperations, evaluationContextProvider,
|
||||
searchTemplateQuery.id());
|
||||
}
|
||||
return new ElasticsearchPartQuery(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
return new RepositoryPartQuery(queryMethod, elasticsearchOperations, evaluationContextProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,10 @@ import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperatio
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryMethod;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactivePartTreeElasticsearchQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveRepositorySearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ReactiveRepositoryStringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.RepositorySearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.querybyexample.ReactiveQueryByExampleElasticsearchExecutor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
@ -161,10 +163,14 @@ public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFa
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
|
||||
return new ReactiveElasticsearchStringQuery(namedQuery, queryMethod, operations,
|
||||
return new ReactiveRepositoryStringQuery(namedQuery, queryMethod, operations,
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new ReactiveElasticsearchStringQuery(queryMethod, operations, evaluationContextProvider);
|
||||
return new ReactiveRepositoryStringQuery(queryMethod, operations, evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedSearchTemplateQuery()) {
|
||||
var searchTemplateQuery = queryMethod.getAnnotatedSearchTemplateQuery();
|
||||
return new ReactiveRepositorySearchTemplateQuery(queryMethod, operations, evaluationContextProvider,
|
||||
searchTemplateQuery.id());
|
||||
} else {
|
||||
return new ReactivePartTreeElasticsearchQuery(queryMethod, operations, evaluationContextProvider);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.ElasticsearchPartQueryIntegrationTests;
|
||||
import org.springframework.data.elasticsearch.core.query.RepositoryPartQueryIntegrationTests;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
|
||||
@ -29,7 +29,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplat
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ElasticsearchPartQueryELCIntegrationTests extends ElasticsearchPartQueryIntegrationTests {
|
||||
public class ElasticsearchPartQueryELCIntegrationTests extends RepositoryPartQueryIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchTemplateConfiguration.class })
|
||||
|
@ -31,15 +31,15 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod;
|
||||
import org.springframework.data.elasticsearch.repository.query.RepositoryPartQuery;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticsearchPartQuery}. The tests make sure that queries are built according to the method naming.
|
||||
* Tests for {@link RepositoryPartQuery}. The tests make sure that queries are built according to the method naming.
|
||||
* Classes implementing this abstract class are in the packages of their request factories and converters as these are
|
||||
* kept package private.
|
||||
*
|
||||
@ -48,7 +48,7 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
@SpringIntegrationTest
|
||||
public abstract class ElasticsearchPartQueryIntegrationTests {
|
||||
public abstract class RepositoryPartQueryIntegrationTests {
|
||||
|
||||
public static final String BOOK_TITLE = "Title";
|
||||
public static final int BOOK_PRICE = 42;
|
||||
@ -646,7 +646,7 @@ public abstract class ElasticsearchPartQueryIntegrationTests {
|
||||
ElasticsearchQueryMethod queryMethod = new ElasticsearchQueryMethod(method,
|
||||
new DefaultRepositoryMetadata(SampleRepository.class), new SpelAwareProxyProjectionFactory(),
|
||||
operations.getElasticsearchConverter().getMappingContext());
|
||||
ElasticsearchPartQuery partQuery = new ElasticsearchPartQuery(queryMethod, operations,
|
||||
RepositoryPartQuery partQuery = new RepositoryPartQuery(queryMethod, operations,
|
||||
QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
Query query = partQuery.createQuery(parameters);
|
||||
return buildQueryString(query, Book.class);
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2025 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.repository.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class ElasticsearchStringQueryUnitTestBase {
|
||||
|
||||
protected ElasticsearchConverter setupConverter() {
|
||||
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(
|
||||
new SimpleElasticsearchMappingContext());
|
||||
Collection<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(ElasticsearchStringQueryUnitTests.CarConverter.INSTANCE);
|
||||
CustomConversions customConversions = new ElasticsearchCustomConversions(converters);
|
||||
converter.setConversions(customConversions);
|
||||
converter.afterPropertiesSet();
|
||||
return converter;
|
||||
}
|
||||
|
||||
static class Car {
|
||||
@Nullable private String name;
|
||||
@Nullable private String model;
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(@Nullable String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
enum CarConverter implements Converter<Car, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(ElasticsearchStringQueryUnitTests.Car car) {
|
||||
return (car.getName() != null ? car.getName() : "null") + '-'
|
||||
+ (car.getModel() != null ? car.getModel() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ReactiveRepositoryQueryUnitTestsBase {
|
||||
|
||||
@Mock ReactiveElasticsearchOperations operations;
|
||||
|
||||
/**
|
||||
* set up the {operations} mock to return the {@link ElasticsearchConverter} from setupConverter().
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a simple {@link MappingElasticsearchConverter} with no special setup.
|
||||
*/
|
||||
protected MappingElasticsearchConverter setupConverter() {
|
||||
return new MappingElasticsearchConverter(
|
||||
new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveElasticsearchQueryMethod} for the given method
|
||||
*
|
||||
* @param repositoryClass
|
||||
* @param name
|
||||
* @param parameters
|
||||
* @return
|
||||
* @throws NoSuchMethodException
|
||||
*/
|
||||
|
||||
protected ReactiveElasticsearchQueryMethod getQueryMethod(Class<?> repositoryClass, String name,
|
||||
Class<?>... parameters)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Method method = repositoryClass.getMethod(name, parameters);
|
||||
return new ReactiveElasticsearchQueryMethod(method,
|
||||
new DefaultRepositoryMetadata(repositoryClass),
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class ReactiveRepositorySearchTemplateQueryUnitTests extends ReactiveRepositoryQueryUnitTestsBase {
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should set searchtemplate id")
|
||||
void shouldSetSearchTemplateId() throws NoSuchMethodException {
|
||||
|
||||
var query = createQuery("searchWithArgs", "answer", 42);
|
||||
|
||||
assertThat(query).isInstanceOf(org.springframework.data.elasticsearch.core.query.SearchTemplateQuery.class);
|
||||
var searchTemplateQuery = (org.springframework.data.elasticsearch.core.query.SearchTemplateQuery) query;
|
||||
|
||||
assertThat(searchTemplateQuery.getId()).isEqualTo("searchtemplate-42");
|
||||
}
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should set searchtemplate parameters")
|
||||
void shouldSetSearchTemplateParameters() throws NoSuchMethodException {
|
||||
|
||||
var query = createQuery("searchWithArgs", "answer", 42);
|
||||
|
||||
assertThat(query).isInstanceOf(org.springframework.data.elasticsearch.core.query.SearchTemplateQuery.class);
|
||||
var searchTemplateQuery = (org.springframework.data.elasticsearch.core.query.SearchTemplateQuery) query;
|
||||
|
||||
var params = searchTemplateQuery.getParams();
|
||||
assertThat(params).isNotNull().hasSize(2);
|
||||
assertThat(params.get("stringArg")).isEqualTo("answer");
|
||||
assertThat(params.get("intArg")).isEqualTo(42);
|
||||
}
|
||||
|
||||
// region helper methods
|
||||
private Query createQuery(String methodName, Object... args) throws NoSuchMethodException {
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(SampleRepository.class, methodName, argTypes);
|
||||
|
||||
ReactiveRepositorySearchTemplateQuery repositorySearchTemplateQuery = queryForMethod(queryMethod);
|
||||
|
||||
return repositorySearchTemplateQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
|
||||
}
|
||||
|
||||
private ReactiveRepositorySearchTemplateQuery queryForMethod(ReactiveElasticsearchQueryMethod queryMethod) {
|
||||
return new ReactiveRepositorySearchTemplateQuery(queryMethod, operations, QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
queryMethod.getAnnotatedSearchTemplateQuery().id());
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region test data
|
||||
private interface SampleRepository extends ElasticsearchRepository<SampleEntity, String> {
|
||||
@SearchTemplateQuery(id = "searchtemplate-42")
|
||||
SearchHits<SampleEntity> searchWithArgs(String stringArg, Integer intArg);
|
||||
|
||||
@SearchTemplateQuery(id = "searchtemplate-42")
|
||||
SearchHits<SampleEntity> searchWithArgsAndSort(String stringArg, Integer intArg, Sort sort);
|
||||
}
|
||||
|
||||
@Document(indexName = "not-relevant")
|
||||
static class SampleEntity {
|
||||
@Nullable
|
||||
@Id String id;
|
||||
@Nullable String data;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(@Nullable String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -16,12 +16,10 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -29,28 +27,27 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repositories.custommethod.QueryParameter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@ -60,13 +57,54 @@ import org.springframework.lang.Nullable;
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase {
|
||||
public class ReactiveRepositoryStringQueryUnitTests extends ReactiveRepositoryQueryUnitTestsBase {
|
||||
|
||||
@Mock ReactiveElasticsearchOperations operations;
|
||||
/**
|
||||
* Adds some data class and custom conversion to the base class implementation.
|
||||
*/
|
||||
protected MappingElasticsearchConverter setupConverter() {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
Collection<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(CarConverter.INSTANCE);
|
||||
CustomConversions customConversions = new ElasticsearchCustomConversions(converters);
|
||||
|
||||
MappingElasticsearchConverter converter = super.setupConverter();
|
||||
converter.setConversions(customConversions);
|
||||
converter.afterPropertiesSet();
|
||||
return converter;
|
||||
}
|
||||
|
||||
static class Car {
|
||||
@Nullable private String name;
|
||||
@Nullable private String model;
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(@Nullable String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
enum CarConverter implements Converter<Car, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Car car) {
|
||||
return (car.getName() != null ? car.getName() : "null") + '-'
|
||||
+ (car.getModel() != null ? car.getModel() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
@ -367,31 +405,23 @@ public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStri
|
||||
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass)
|
||||
.map(clazz -> Collection.class.isAssignableFrom(clazz) ? List.class : clazz).toArray(Class[]::new);
|
||||
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(methodName, argTypes);
|
||||
ReactiveElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
|
||||
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(SampleRepository.class, methodName, argTypes);
|
||||
ReactiveRepositoryStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
|
||||
|
||||
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchStringQuery queryForMethod(ReactiveElasticsearchQueryMethod queryMethod) {
|
||||
return new ReactiveElasticsearchStringQuery(queryMethod, operations,
|
||||
QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
private ReactiveRepositoryStringQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
private ReactiveElasticsearchQueryMethod getQueryMethod(String name, Class<?>... parameters)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new ReactiveElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchStringQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(name, parameters);
|
||||
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(SampleRepository.class, name, parameters);
|
||||
return queryForMethod(queryMethod);
|
||||
}
|
||||
|
||||
private ReactiveRepositoryStringQuery queryForMethod(ReactiveElasticsearchQueryMethod queryMethod) {
|
||||
return new ReactiveRepositoryStringQuery(queryMethod, operations,
|
||||
QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<Person, String> {
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RepositoryQueryUnitTestsBase {
|
||||
|
||||
@Mock ElasticsearchOperations operations;
|
||||
|
||||
/**
|
||||
* set up the {operations} mock to return the {@link ElasticsearchConverter} from setupConverter().
|
||||
*/
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a simple {@link MappingElasticsearchConverter} with no special setup.
|
||||
*/
|
||||
protected MappingElasticsearchConverter setupConverter() {
|
||||
return new MappingElasticsearchConverter(
|
||||
new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ElasticsearchQueryMethod} for the given method
|
||||
*
|
||||
* @param repositoryClass
|
||||
* @param name
|
||||
* @param parameters
|
||||
* @return
|
||||
* @throws NoSuchMethodException
|
||||
*/
|
||||
protected ElasticsearchQueryMethod getQueryMethod(Class<?> repositoryClass, String name, Class<?>... parameters)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Method method = repositoryClass.getMethod(name, parameters);
|
||||
return new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(repositoryClass),
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class RepositorySearchTemplateQueryUnitTests extends RepositoryQueryUnitTestsBase {
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should set searchtemplate id")
|
||||
void shouldSetSearchTemplateId() throws NoSuchMethodException {
|
||||
|
||||
var query = createQuery("searchWithArgs", "answer", 42);
|
||||
|
||||
assertThat(query).isInstanceOf(org.springframework.data.elasticsearch.core.query.SearchTemplateQuery.class);
|
||||
var searchTemplateQuery = (org.springframework.data.elasticsearch.core.query.SearchTemplateQuery) query;
|
||||
|
||||
assertThat(searchTemplateQuery.getId()).isEqualTo("searchtemplate-42");
|
||||
}
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should set searchtemplate parameters")
|
||||
void shouldSetSearchTemplateParameters() throws NoSuchMethodException {
|
||||
|
||||
var query = createQuery("searchWithArgs", "answer", 42);
|
||||
|
||||
assertThat(query).isInstanceOf(org.springframework.data.elasticsearch.core.query.SearchTemplateQuery.class);
|
||||
var searchTemplateQuery = (org.springframework.data.elasticsearch.core.query.SearchTemplateQuery) query;
|
||||
|
||||
var params = searchTemplateQuery.getParams();
|
||||
assertThat(params).isNotNull().hasSize(2);
|
||||
assertThat(params.get("stringArg")).isEqualTo("answer");
|
||||
assertThat(params.get("intArg")).isEqualTo(42);
|
||||
}
|
||||
|
||||
// region helper methods
|
||||
private Query createQuery(String methodName, Object... args) throws NoSuchMethodException {
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||
ElasticsearchQueryMethod queryMethod = getQueryMethod(SampleRepository.class, methodName, argTypes);
|
||||
|
||||
RepositorySearchTemplateQuery repositorySearchTemplateQuery = queryForMethod(queryMethod);
|
||||
|
||||
return repositorySearchTemplateQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
|
||||
}
|
||||
|
||||
private RepositorySearchTemplateQuery queryForMethod(ElasticsearchQueryMethod queryMethod) {
|
||||
return new RepositorySearchTemplateQuery(queryMethod, operations, QueryMethodEvaluationContextProvider.DEFAULT,
|
||||
queryMethod.getAnnotatedSearchTemplateQuery().id());
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region test data
|
||||
private interface SampleRepository extends ElasticsearchRepository<SampleEntity, String> {
|
||||
@SearchTemplateQuery(id = "searchtemplate-42")
|
||||
SearchHits<SampleEntity> searchWithArgs(String stringArg, Integer intArg);
|
||||
|
||||
@SearchTemplateQuery(id = "searchtemplate-42")
|
||||
SearchHits<SampleEntity> searchWithArgsAndSort(String stringArg, Integer intArg, Sort sort);
|
||||
}
|
||||
|
||||
@Document(indexName = "not-relevant")
|
||||
static class SampleEntity {
|
||||
@Nullable
|
||||
@Id String id;
|
||||
@Nullable String data;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(@Nullable String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -16,9 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -26,28 +24,25 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repositories.custommethod.QueryParameter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@ -57,14 +52,53 @@ import org.springframework.lang.Nullable;
|
||||
* @author Niklas Herder
|
||||
* @author Haibo Liu
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase {
|
||||
public class RepositoryStringQueryUnitTests extends RepositoryStringQueryUnitTestsBase {
|
||||
/**
|
||||
* Adds some data class and custom conversion to the base class implementation.
|
||||
*/
|
||||
protected MappingElasticsearchConverter setupConverter() {
|
||||
|
||||
@Mock ElasticsearchOperations operations;
|
||||
Collection<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(RepositoryStringQueryUnitTests.CarConverter.INSTANCE);
|
||||
CustomConversions customConversions = new ElasticsearchCustomConversions(converters);
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
MappingElasticsearchConverter converter = super.setupConverter();
|
||||
converter.setConversions(customConversions);
|
||||
converter.afterPropertiesSet();
|
||||
return converter;
|
||||
}
|
||||
|
||||
static class Car {
|
||||
@Nullable private String name;
|
||||
@Nullable private String model;
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(@Nullable String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
enum CarConverter implements Converter<Car, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Car car) {
|
||||
return (car.getName() != null ? car.getName() : "null") + '-'
|
||||
+ (car.getModel() != null ? car.getModel() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAES-552
|
||||
@ -350,8 +384,9 @@ public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryU
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||
ElasticsearchQueryMethod queryMethod = getQueryMethod(methodName, argTypes);
|
||||
ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
|
||||
ElasticsearchQueryMethod queryMethod = getQueryMethod(RepositoryStringQueryUnitTests.SampleRepository.class,
|
||||
methodName, argTypes);
|
||||
RepositoryStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
|
||||
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
|
||||
}
|
||||
|
||||
@ -370,18 +405,11 @@ public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryU
|
||||
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }");
|
||||
}
|
||||
|
||||
private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) {
|
||||
return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery(),
|
||||
private RepositoryStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) {
|
||||
return new RepositoryStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery(),
|
||||
QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
private ElasticsearchQueryMethod getQueryMethod(String name, Class<?>... parameters) throws NoSuchMethodException {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<Person, String> {
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }")
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021-2025 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.repository.query;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class RepositoryStringQueryUnitTestsBase extends RepositoryQueryUnitTestsBase {
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.support;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @since 5.5
|
||||
*/
|
||||
@ContextConfiguration(classes = ReactiveRepositoryQueryELCIntegrationTests.Config.class)
|
||||
public class ReactiveRepositoryQueryELCIntegrationTests
|
||||
extends ReactiveRepositoryQueryIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-repository-query");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.elasticsearch.core.IndexOperationsAdapter.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @since 5.5
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
abstract class ReactiveRepositoryQueryIntegrationTests {
|
||||
@Autowired private SampleElasticsearchRepository repository;
|
||||
@Autowired private ReactiveElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
indexNameProvider.increment();
|
||||
blocking(operations.indexOps(LOTRCharacter.class)).createWithMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
|
||||
public void cleanup() {
|
||||
blocking(operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*"))).delete();
|
||||
}
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should use searchtemplate query")
|
||||
void shouldUseSearchtemplateQuery() {
|
||||
// store some data
|
||||
repository.saveAll(List.of(
|
||||
new LOTRCharacter("1", "Frodo is a hobbit"),
|
||||
new LOTRCharacter("2", "Legolas is an elf"),
|
||||
new LOTRCharacter("3", "Gandalf is a wizard"),
|
||||
new LOTRCharacter("4", "Bilbo is a hobbit"),
|
||||
new LOTRCharacter("5", "Gimli is a dwarf")))
|
||||
.blockLast();
|
||||
|
||||
// store a searchtemplate
|
||||
String searchInCharacter = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"lotrCharacter": "{{word}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": 0,
|
||||
"size": 100,
|
||||
"sort": {
|
||||
"id": {
|
||||
"order": "desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Script scriptSearchInCharacter = Script.builder() //
|
||||
.withId("searchInCharacter") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(searchInCharacter) //
|
||||
.build();
|
||||
|
||||
var success = operations.putScript(scriptSearchInCharacter).block();
|
||||
assertThat(success).isTrue();
|
||||
|
||||
// search with repository for hobbits order by id descending
|
||||
var searchHits = repository.searchInCharacter("hobbit")
|
||||
.collectList().block();
|
||||
|
||||
// check result (bilbo, frodo)
|
||||
assertThat(searchHits).isNotNull();
|
||||
assertThat(searchHits.size()).isEqualTo(2);
|
||||
assertThat(searchHits.get(0).getId()).isEqualTo("4");
|
||||
assertThat(searchHits.get(1).getId()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class LOTRCharacter {
|
||||
@Nullable
|
||||
@Id
|
||||
@Field(fielddata = true) // needed for the sort to work
|
||||
private String id;
|
||||
|
||||
@Field(type = FieldType.Text)
|
||||
@Nullable private String lotrCharacter;
|
||||
|
||||
public LOTRCharacter(@Nullable String id, @Nullable String lotrCharacter) {
|
||||
this.id = id;
|
||||
this.lotrCharacter = lotrCharacter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLotrCharacter() {
|
||||
return lotrCharacter;
|
||||
}
|
||||
|
||||
public void setLotrCharacter(@Nullable String lotrCharacter) {
|
||||
this.lotrCharacter = lotrCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleElasticsearchRepository
|
||||
extends ReactiveElasticsearchRepository<LOTRCharacter, String> {
|
||||
@SearchTemplateQuery(id = "searchInCharacter")
|
||||
Flux<SearchHit<LOTRCharacter>> searchInCharacter(String word);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.support;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@ContextConfiguration(classes = {RepositoryQueryELCIntegrationTests.Config.class })public class RepositoryQueryELCIntegrationTests extends RepositoryQueryIntegrationTests {
|
||||
@Configuration
|
||||
@Import({ElasticsearchTemplateConfiguration.class })
|
||||
@EnableElasticsearchRepositories(basePackages = {"org.springframework.data.elasticsearch.repository.support" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("repository-query");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2025 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.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.SearchTemplateQuery;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@SpringIntegrationTest
|
||||
abstract class RepositoryQueryIntegrationTests {
|
||||
@Autowired private SampleElasticsearchRepository repository;
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(LOTRCharacter.class).createWithMapping();
|
||||
}
|
||||
|
||||
@Test
|
||||
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
|
||||
public void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Test // #2997
|
||||
@DisplayName("should use searchtemplate query")
|
||||
void shouldUseSearchtemplateQuery() {
|
||||
// store some data
|
||||
repository.saveAll(List.of(
|
||||
new LOTRCharacter("1", "Frodo is a hobbit"),
|
||||
new LOTRCharacter("2", "Legolas is an elf"),
|
||||
new LOTRCharacter("3", "Gandalf is a wizard"),
|
||||
new LOTRCharacter("4", "Bilbo is a hobbit"),
|
||||
new LOTRCharacter("5", "Gimli is a dwarf")));
|
||||
|
||||
// store a searchtemplate
|
||||
String searchInCharacter = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match": {
|
||||
"lotrCharacter": "{{word}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"from": 0,
|
||||
"size": 100,
|
||||
"sort": {
|
||||
"id": {
|
||||
"order": "desc"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Script scriptSearchInCharacter = Script.builder() //
|
||||
.withId("searchInCharacter") //
|
||||
.withLanguage("mustache") //
|
||||
.withSource(searchInCharacter) //
|
||||
.build();
|
||||
|
||||
var success = operations.putScript(scriptSearchInCharacter);
|
||||
assertThat(success).isTrue();
|
||||
|
||||
// search with repository for hobbits order by id descending
|
||||
var searchHits = repository.searchInCharacter("hobbit");
|
||||
|
||||
// check result (bilbo, frodo)
|
||||
assertThat(searchHits).isNotNull();
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(2);
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo("4");
|
||||
assertThat(searchHits.getSearchHit(1).getId()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class LOTRCharacter {
|
||||
@Nullable
|
||||
@Id
|
||||
@Field(fielddata = true) // needed for the sort to work
|
||||
private String id;
|
||||
|
||||
@Field(type = FieldType.Text)
|
||||
@Nullable private String lotrCharacter;
|
||||
|
||||
public LOTRCharacter(@Nullable String id, @Nullable String lotrCharacter) {
|
||||
this.id = id;
|
||||
this.lotrCharacter = lotrCharacter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLotrCharacter() {
|
||||
return lotrCharacter;
|
||||
}
|
||||
|
||||
public void setLotrCharacter(@Nullable String lotrCharacter) {
|
||||
this.lotrCharacter = lotrCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleElasticsearchRepository
|
||||
extends ElasticsearchRepository<LOTRCharacter, String> {
|
||||
@SearchTemplateQuery(id = "searchInCharacter")
|
||||
SearchHits<LOTRCharacter> searchInCharacter(String word);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user