DATAES-176 - firstN repository queries.

Original PR: #305
This commit is contained in:
Peter-Josef Meisch 2019-08-20 18:34:11 +02:00
parent b4ebca7ab5
commit d864ff1292
9 changed files with 143 additions and 32 deletions

View File

@ -362,16 +362,19 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate
@Override
public <T> T queryForObject(CriteriaQuery query, Class<T> clazz) {
Page<T> 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> T queryForObject(StringQuery query, Class<T> clazz) {
Page<T> 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> T getObjectFromPage(Page<T> 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());
}

View File

@ -294,16 +294,19 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate impleme
@Override
public <T> T queryForObject(CriteriaQuery query, Class<T> clazz) {
Page<T> 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> T queryForObject(StringQuery query, Class<T> clazz) {
Page<T> 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> T getObjectFromPage(Page<T> 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());
}

View File

@ -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()));
}

View File

@ -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 extends Query> 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<String> 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;
}
}

View File

@ -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 <tt>false</tt>.
*
*
* @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;
}
}

View File

@ -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());

View File

@ -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

View File

@ -88,7 +88,6 @@ public class ElasticsearchQueryCreator extends AbstractQueryCreator<CriteriaQuer
protected CriteriaQuery complete(@Nullable CriteriaQuery query, Sort sort) {
if (query == null) {
// this is the case in a findAllByOrderByField method, add empty criteria
query = new CriteriaQuery(new Criteria());
}

View File

@ -61,15 +61,15 @@ abstract class QueryKeywordsTests {
IndexInitializer.init(elasticsearchTemplate, Product.class);
Product product1 = Product.builder().id("1").name("Sugar").text("Cane sugar").price(1.0f).available(false)
Product product1 = Product.builder().id("1").name("Sugar").text("Cane sugar").price(1.0f).available(false)
.sortName("sort5").build();
Product product2 = Product.builder().id("2").name("Sugar").text("Cane sugar").price(1.2f).available(true)
Product product2 = Product.builder().id("2").name("Sugar").text("Cane sugar").price(1.2f).available(true)
.sortName("sort4").build();
Product product3 = Product.builder().id("3").name("Sugar").text("Beet sugar").price(1.1f).available(true)
Product product3 = Product.builder().id("3").name("Sugar").text("Beet sugar").price(1.1f).available(true)
.sortName("sort3").build();
Product product4 = Product.builder().id("4").name("Salt").text("Rock salt").price(1.9f).available(true)
Product product4 = Product.builder().id("4").name("Salt").text("Rock salt").price(1.9f).available(true)
.sortName("sort2").build();
Product product5 = Product.builder().id("5").name("Salt").text("Sea salt").price(2.1f).available(false)
Product product5 = Product.builder().id("5").name("Salt").text("Sea salt").price(2.1f).available(false)
.sortName("sort1").build();
repository.saveAll(Arrays.asList(product1, product2, product3, product4, product5));
@ -165,7 +165,7 @@ abstract class QueryKeywordsTests {
@Test // DATAES-615
public void shouldSupportSortOnStandardFieldWithCriteria() {
List<String> sortedIds = repository.findAllByNameOrderByText("Salt").stream() //
List<String> 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<String> sortedIds = repository.findAllByNameOrderBySortName("Sugar").stream() //
List<String> 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<String> sortedIds = repository.findAllByOrderByText().stream() //
List<String> 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<String> sortedIds = repository.findAllByOrderBySortName().stream() //
List<String> 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<Product> products = repository.findFirst2ByName("Sugar");
assertThat(products).hasSize(2);
products.forEach(product -> assertThat(product.name).isEqualTo("Sugar"));
}
@Test // DATAES-178
public void shouldReturnTwoWithFindTop2() {
List<Product> 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<Product> findAllByOrderByText();
List<Product> findAllByOrderBySortName();
Product findFirstByName(String name);
Product findTopByName(String name);
List<Product> findFirst2ByName(String name);
List<Product> findTop2ByName(String name);
}
}