diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 37f1811f9..fa0fc3cec 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -362,16 +362,19 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate @Override public T queryForObject(CriteriaQuery query, Class clazz) { - Page page = queryForPage(query, clazz); - Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results"); - return page.getTotalElements() > 0 ? page.getContent().get(0) : null; + return getObjectFromPage(queryForPage(query, clazz)); } @Override public T queryForObject(StringQuery query, Class clazz) { - Page page = queryForPage(query, clazz); - Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results"); - return page.getTotalElements() > 0 ? page.getContent().get(0) : null; + return getObjectFromPage(queryForPage(query, clazz)); + } + + @Nullable + private T getObjectFromPage(Page page) { + int contentSize = page.getContent().size(); + Assert.isTrue(contentSize < 2, "Expected 1 but found " + contentSize + " results"); + return contentSize > 0 ? page.getContent().get(0) : null; } @Override @@ -492,19 +495,24 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate QueryBuilder elasticsearchFilter = new CriteriaFilterProcessor() .createFilterFromCriteria(criteriaQuery.getCriteria()); SearchRequest request = prepareSearch(criteriaQuery, clazz); + SearchSourceBuilder sourceBuilder = request.source(); if (elasticsearchQuery != null) { - request.source().query(elasticsearchQuery); + sourceBuilder.query(elasticsearchQuery); } else { - request.source().query(QueryBuilders.matchAllQuery()); + sourceBuilder.query(QueryBuilders.matchAllQuery()); + } + + if (criteriaQuery.isLimiting()) { + sourceBuilder.size(criteriaQuery.getMaxResults()); } if (criteriaQuery.getMinScore() > 0) { - request.source().minScore(criteriaQuery.getMinScore()); + sourceBuilder.minScore(criteriaQuery.getMinScore()); } if (elasticsearchFilter != null) - request.source().postFilter(elasticsearchFilter); + sourceBuilder.postFilter(elasticsearchFilter); if (logger.isDebugEnabled()) { logger.debug("doSearch query:\n" + request.toString()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 6cf4d9c6d..bb2eceb65 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -294,16 +294,19 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate impleme @Override public T queryForObject(CriteriaQuery query, Class clazz) { - Page page = queryForPage(query, clazz); - Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results"); - return page.getTotalElements() > 0 ? page.getContent().get(0) : null; + return getObjectFromPage(queryForPage(query, clazz)); } @Override public T queryForObject(StringQuery query, Class clazz) { - Page page = queryForPage(query, clazz); - Assert.isTrue(page.getTotalElements() < 2, "Expected 1 but found " + page.getTotalElements() + " results"); - return page.getTotalElements() > 0 ? page.getContent().get(0) : null; + return getObjectFromPage(queryForPage(query, clazz)); + } + + @Nullable + private T getObjectFromPage(Page page) { + int contentSize = page.getContent().size(); + Assert.isTrue(contentSize < 2, "Expected 1 but found " + contentSize + " results"); + return contentSize > 0 ? page.getContent().get(0) : null; } @Override @@ -423,6 +426,10 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate impleme searchRequestBuilder.setQuery(QueryBuilders.matchAllQuery()); } + if (criteriaQuery.isLimiting()) { + searchRequestBuilder.setSize(criteriaQuery.getMaxResults()); + } + if (criteriaQuery.getMinScore() > 0) { searchRequestBuilder.setMinScore(criteriaQuery.getMinScore()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index f89559723..c95a67cd0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -294,7 +294,12 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera request.source(searchSourceBuilder); return doFind(prepareSearchRequest(request)); + } else if (query.isLimiting()) { + searchSourceBuilder.from(0); + searchSourceBuilder.size(query.getMaxResults()); + request.source(searchSourceBuilder); + return doFind(prepareSearchRequest(request)); } else { request.source(searchSourceBuilder); @@ -657,9 +662,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera elasticsearchQuery = new WrapperQueryBuilder(((StringQuery) query).getSource()); } else if (query instanceof NativeSearchQuery) { elasticsearchQuery = ((NativeSearchQuery) query).getQuery(); - } - - else { + } else { throw new IllegalArgumentException(String.format("Unknown query type '%s'.", query.getClass())); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 32e518ef9..52968b281 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -36,6 +36,7 @@ import org.springframework.util.Assert; * @author Alen Turkovic * @author Sascha Woo * @author Farid Azaza + * @author Peter-Josef Meisch */ abstract class AbstractQuery implements Query { @@ -52,6 +53,7 @@ abstract class AbstractQuery implements Query { protected IndicesOptions indicesOptions; protected boolean trackScores; protected String preference; + protected Integer maxResults; @Override public Sort getSort() { @@ -112,6 +114,7 @@ abstract class AbstractQuery implements Query { return sourceFilter; } + @Override @SuppressWarnings("unchecked") public final T addSort(Sort sort) { if (sort == null) { @@ -127,6 +130,7 @@ abstract class AbstractQuery implements Query { return (T) this; } + @Override public float getMinScore() { return minScore; } @@ -135,6 +139,7 @@ abstract class AbstractQuery implements Query { this.minScore = minScore; } + @Override public Collection getIds() { return ids; } @@ -143,6 +148,7 @@ abstract class AbstractQuery implements Query { this.ids = ids; } + @Override public String getRoute() { return route; } @@ -155,10 +161,12 @@ abstract class AbstractQuery implements Query { this.searchType = searchType; } + @Override public SearchType getSearchType() { return searchType; } + @Override public IndicesOptions getIndicesOptions() { return indicesOptions; } @@ -178,7 +186,7 @@ abstract class AbstractQuery implements Query { /** * Configures whether to track scores. - * + * * @param trackScores * @since 3.1 */ @@ -195,4 +203,18 @@ abstract class AbstractQuery implements Query { public void setPreference(String preference) { this.preference = preference; } + + @Override + public boolean isLimiting() { + return maxResults != null; + } + + @Override + public Integer getMaxResults() { + return maxResults; + } + + public void setMaxResults(Integer maxResults) { + this.maxResults = maxResults; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java index 9b7555a47..b21428a68 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java @@ -35,6 +35,7 @@ import org.springframework.data.domain.Sort; * @author Sascha Woo * @author Christoph Strobl * @author Farid Azaza + * @author Peter-Josef Meisch */ public interface Query { @@ -145,7 +146,7 @@ public interface Query { /** * Get if scores will be computed and tracked, regardless of whether sorting on a field. Defaults to false. - * + * * @return * @since 3.1 */ @@ -194,4 +195,22 @@ public interface Query { * @since 3.2 */ void setPreference(String preference); + + /** + * @return true if the query has a limit on the max number of results. + * @since 4.0 + */ + default boolean isLimiting() { + return false; + } + + /** + * return the max of results. Must not return null when {@link #isLimiting()} returns true. + * + * @since 4.0 + */ + default Integer getMaxResults() { + return null; + } + } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java index 331748be3..b9c480e5e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchPartQuery.java @@ -58,6 +58,10 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery CriteriaQuery query = createQuery(accessor); Assert.notNull(query, "unsupported query"); + if (tree.isLimiting()) { + query.setMaxResults(tree.getMaxResults()); + } + if (tree.isDelete()) { Object result = countOrGetDocumentsForDelete(query, accessor); elasticsearchOperations.delete(query, queryMethod.getEntityInformation().getJavaType()); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java index e764414de..88ee4d9c4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.repository.query; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator; import org.springframework.data.repository.query.ResultProcessor; @@ -23,6 +24,7 @@ import org.springframework.data.repository.query.parser.PartTree; /** * @author Christoph Strobl + * @author Peter-Josef Meisch * @since 3.2 */ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElasticsearchRepositoryQuery { @@ -40,7 +42,12 @@ public class ReactivePartTreeElasticsearchQuery extends AbstractReactiveElastics @Override protected Query createQuery(ElasticsearchParameterAccessor accessor) { - return new ElasticsearchQueryCreator(tree, accessor, getMappingContext()).createQuery(); + CriteriaQuery query = new ElasticsearchQueryCreator(tree, accessor, getMappingContext()).createQuery(); + + if (tree.isLimiting()) { + query.setMaxResults(tree.getMaxResults()); + } + return query; } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java index 0f823bf4b..ab5490224 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/parser/ElasticsearchQueryCreator.java @@ -88,7 +88,6 @@ public class ElasticsearchQueryCreator extends AbstractQueryCreator sortedIds = repository.findAllByNameOrderByText("Salt").stream() // + List sortedIds = repository.findAllByNameOrderByText("Salt").stream() // .map(it -> it.id).collect(Collectors.toList()); assertThat(sortedIds).containsExactly("4", "5"); @@ -174,7 +174,7 @@ abstract class QueryKeywordsTests { @Test // DATAES-615 public void shouldSupportSortOnFieldWithCustomFieldNameWithCriteria() { - List sortedIds = repository.findAllByNameOrderBySortName("Sugar").stream() // + List sortedIds = repository.findAllByNameOrderBySortName("Sugar").stream() // .map(it -> it.id).collect(Collectors.toList()); assertThat(sortedIds).containsExactly("3", "2", "1"); @@ -182,7 +182,7 @@ abstract class QueryKeywordsTests { @Test // DATAES-615 public void shouldSupportSortOnStandardFieldWithoutCriteria() { - List sortedIds = repository.findAllByOrderByText().stream() // + List sortedIds = repository.findAllByOrderByText().stream() // .map(it -> it.text).collect(Collectors.toList()); assertThat(sortedIds).containsExactly("Beet sugar", "Cane sugar", "Cane sugar", "Rock salt", "Sea salt"); @@ -191,12 +191,46 @@ abstract class QueryKeywordsTests { @Test // DATAES-615 public void shouldSupportSortOnFieldWithCustomFieldNameWithoutCriteria() { - List sortedIds = repository.findAllByOrderBySortName().stream() // + List sortedIds = repository.findAllByOrderBySortName().stream() // .map(it -> it.id).collect(Collectors.toList()); assertThat(sortedIds).containsExactly("5", "4", "3", "2", "1"); } + @Test // DATAES-178 + public void shouldReturnOneWithFindFirst() { + + Product product = repository.findFirstByName("Sugar"); + + assertThat(product.name).isEqualTo("Sugar"); + } + + @Test // DATAES-178 + public void shouldReturnOneWithFindTop() { + + Product product = repository.findTopByName("Sugar"); + + assertThat(product.name).isEqualTo("Sugar"); + } + + @Test // DATAES-178 + public void shouldReturnTwoWithFindFirst2() { + + List products = repository.findFirst2ByName("Sugar"); + + assertThat(products).hasSize(2); + products.forEach(product -> assertThat(product.name).isEqualTo("Sugar")); + } + + @Test // DATAES-178 + public void shouldReturnTwoWithFindTop2() { + + List products = repository.findTop2ByName("Sugar"); + + assertThat(products).hasSize(2); + products.forEach(product -> assertThat(product.name).isEqualTo("Sugar")); + } + /** * @author Mohsin Husen * @author Artur Konczak @@ -279,6 +313,14 @@ abstract class QueryKeywordsTests { List findAllByOrderByText(); List findAllByOrderBySortName(); + + Product findFirstByName(String name); + + Product findTopByName(String name); + + List findFirst2ByName(String name); + + List findTop2ByName(String name); } }