Add support for fluent limit(int) and scroll(OffsetScrollPosition) to Query by Example queries.

Closes #1609
This commit is contained in:
Mark Paluch
2023-09-14 08:40:56 +02:00
parent 6d1d96681f
commit bd1c670d55
8 changed files with 387 additions and 70 deletions

View File

@@ -15,25 +15,32 @@
*/
package org.springframework.data.jdbc.repository.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.relational.core.query.Query;
import org.springframework.data.relational.repository.query.RelationalExampleMapper;
import org.springframework.util.Assert;
/**
* {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using {@link Example}.
*
* @author Diego Krupitza
* @author Mark Paluch
* @since 3.0
*/
class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> {
@@ -43,13 +50,13 @@ class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> {
FetchableFluentQueryByExample(Example<S> example, Class<R> resultType, RelationalExampleMapper exampleMapper,
JdbcAggregateOperations entityOperations) {
this(example, Sort.unsorted(), resultType, Collections.emptyList(), exampleMapper, entityOperations);
this(example, Sort.unsorted(), 0, resultType, Collections.emptyList(), exampleMapper, entityOperations);
}
FetchableFluentQueryByExample(Example<S> example, Sort sort, Class<R> resultType, List<String> fieldsToInclude,
RelationalExampleMapper exampleMapper, JdbcAggregateOperations entityOperations) {
FetchableFluentQueryByExample(Example<S> example, Sort sort, int limit, Class<R> resultType,
List<String> fieldsToInclude, RelationalExampleMapper exampleMapper, JdbcAggregateOperations entityOperations) {
super(example, sort, resultType, fieldsToInclude);
super(example, sort, limit, resultType, fieldsToInclude);
this.exampleMapper = exampleMapper;
this.entityOperations = entityOperations;
@@ -71,10 +78,40 @@ class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> {
@Override
public List<R> all() {
return findAll(createQuery().sort(getSort()));
}
return StreamSupport
.stream(this.entityOperations.findAll(createQuery().sort(getSort()), getExampleType()).spliterator(), false)
.map(item -> this.getConversionFunction().apply(item)).collect(Collectors.toList());
private List<R> findAll(Query query) {
Function<Object, R> conversionFunction = this.getConversionFunction();
Iterable<S> raw = this.entityOperations.findAll(query, getExampleType());
List<R> result = new ArrayList<>(raw instanceof Collections ? ((Collection<?>) raw).size() : 16);
for (S s : raw) {
result.add(conversionFunction.apply(s));
}
return result;
}
@Override
public Window<R> scroll(ScrollPosition scrollPosition) {
Assert.notNull(scrollPosition, "ScrollPosition must not be null");
if (scrollPosition instanceof OffsetScrollPosition osp) {
Query query = createQuery().sort(getSort()).offset(osp.getOffset());
if (getLimit() > 0) {
query = query.limit(getLimit());
}
return ScrollDelegate.scroll(query, this::findAll, osp);
}
return super.scroll(scrollPosition);
}
@Override
@@ -114,16 +151,18 @@ class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<S, R> {
query = query.columns(getFieldsToInclude().toArray(new String[0]));
}
query = query.limit(getLimit());
query = queryCustomizer.apply(query);
return query;
}
@Override
protected <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, Class<R> resultType,
protected <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, int limit, Class<R> resultType,
List<String> fieldsToInclude) {
return new FetchableFluentQueryByExample<>(example, sort, resultType, fieldsToInclude, this.exampleMapper,
return new FetchableFluentQueryByExample<>(example, sort, limit, resultType, fieldsToInclude, this.exampleMapper,
this.entityOperations);
}
}

View File

@@ -15,6 +15,11 @@
*/
package org.springframework.data.jdbc.repository.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
@@ -22,30 +27,28 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
/**
* Support class for {@link FluentQuery.FetchableFluentQuery} implementations.
*
* @author Diego Krupitza
* @author Mark Paluch
* @since 3.0
*/
abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQuery<R> {
private final Example<S> example;
private final Sort sort;
private final int limit;
private final Class<R> resultType;
private final List<String> fieldsToInclude;
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
FluentQuerySupport(Example<S> example, Sort sort, Class<R> resultType, List<String> fieldsToInclude) {
FluentQuerySupport(Example<S> example, Sort sort, int limit, Class<R> resultType, List<String> fieldsToInclude) {
this.example = example;
this.sort = sort;
this.limit = limit;
this.resultType = resultType;
this.fieldsToInclude = fieldsToInclude;
}
@@ -55,7 +58,15 @@ abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQu
Assert.notNull(sort, "Sort must not be null!");
return create(example, sort, resultType, fieldsToInclude);
return create(example, sort, limit, resultType, fieldsToInclude);
}
@Override
public FetchableFluentQuery<R> limit(int limit) {
Assert.isTrue(limit >= 0, "Limit must not be negative");
return create(example, sort, limit, resultType, fieldsToInclude);
}
@Override
@@ -63,7 +74,7 @@ abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQu
Assert.notNull(projection, "Projection target type must not be null!");
return create(example, sort, projection, fieldsToInclude);
return create(example, sort, limit, projection, fieldsToInclude);
}
@Override
@@ -71,10 +82,10 @@ abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQu
Assert.notNull(properties, "Projection properties must not be null!");
return create(example, sort, resultType, new ArrayList<>(properties));
return create(example, sort, limit, resultType, new ArrayList<>(properties));
}
protected abstract <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, Class<R> resultType,
protected abstract <R> FluentQuerySupport<S, R> create(Example<S> example, Sort sort, int limit, Class<R> resultType,
List<String> fieldsToInclude);
Class<S> getExampleType() {
@@ -89,6 +100,10 @@ abstract class FluentQuerySupport<S, R> implements FluentQuery.FetchableFluentQu
return sort;
}
int getLimit() {
return limit;
}
Class<R> getResultType() {
return resultType;
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2023 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.jdbc.repository.support;
import java.util.List;
import java.util.function.Function;
import java.util.function.IntFunction;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Window;
import org.springframework.data.relational.core.query.Query;
import org.springframework.util.Assert;
/**
* Delegate to run {@link ScrollPosition scroll queries} and create result {@link Window}.
*
* @author Mark Paluch
* @since 3.1.4
*/
public class ScrollDelegate {
/**
* Run the {@link Query} and return a scroll {@link Window}.
*
* @param query must not be {@literal null}.
* @param scrollPosition must not be {@literal null}.
* @return the scroll {@link Window}.
*/
@SuppressWarnings("unchecked")
public static <T> Window<T> scroll(Query query, Function<Query, List<T>> queryFunction,
ScrollPosition scrollPosition) {
Assert.notNull(scrollPosition, "ScrollPosition must not be null");
int limit = query.getLimit();
if (limit > 0 && limit != Integer.MAX_VALUE) {
query = query.limit(limit + 1);
}
List<T> result = queryFunction.apply(query);
if (scrollPosition instanceof OffsetScrollPosition offset) {
return createWindow(result, limit, OffsetScrollPosition.positionFunction(offset.getOffset()));
}
throw new UnsupportedOperationException("ScrollPosition " + scrollPosition + " not supported");
}
private static <T> Window<T> createWindow(List<T> result, int limit,
IntFunction<? extends ScrollPosition> positionFunction) {
return Window.from(getFirst(limit, result), positionFunction, hasMoreElements(result, limit));
}
private static boolean hasMoreElements(List<?> result, int limit) {
return !result.isEmpty() && result.size() > limit;
}
/**
* Return the first {@code count} items from the list.
*
* @param count the number of first elements to be included in the returned list.
* @param list must not be {@literal null}
* @return the returned sublist if the {@code list} is greater {@code count}.
* @param <T> the element type of the lists.
*/
public static <T> List<T> getFirst(int count, List<T> list) {
if (count > 0 && list.size() > count) {
return list.subList(0, count);
}
return list;
}
}