diff --git a/src/main/java/org/springframework/data/repository/support/PageableExecutionSupport.java b/src/main/java/org/springframework/data/repository/support/PageableExecutionSupport.java new file mode 100644 index 000000000..1f070cde2 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/support/PageableExecutionSupport.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 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 + * + * http://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.repository.support; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.util.Assert; + +/** + * Support for query execution using {@link Pageable}. Using {@link PageableExecutionSupport} assumes that data queries + * are cheaper than {@code COUNT} queries and so some cases can take advantage of optimizations. + * + * @author Mark Paluch + * @since 1.13 + */ +public abstract class PageableExecutionSupport { + + private PageableExecutionSupport() {} + + /** + * Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link TotalSupplier} applying + * optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the + * result size and {@link Pageable}. + * + * @param content must not be {@literal null}. + * @param pageable can be {@literal null}. + * @param totalSupplier must not be {@literal null}. + * @return the {@link Page}. + */ + public static Page getPage(List content, Pageable pageable, TotalSupplier totalSupplier) { + + Assert.notNull(content, "Content must not be null!"); + Assert.notNull(totalSupplier, "TotalSupplier must not be null!"); + + if (pageable == null || pageable.getOffset() == 0) { + + if (pageable == null || pageable.getPageSize() > content.size()) { + return new PageImpl(content, pageable, content.size()); + } + + return new PageImpl(content, pageable, totalSupplier.get()); + } + + if (content.size() != 0 && pageable.getPageSize() > content.size()) { + return new PageImpl(content, pageable, pageable.getOffset() + content.size()); + } + + return new PageImpl(content, pageable, totalSupplier.get()); + } + + /** + * Supplies the total count for a particular query. Can be replaced with a Java 8 Supplier when upgrading to Java 8. + * + * @author Mark Paluch + */ + public interface TotalSupplier { + + /** + * @return the total count for a particular query. + */ + long get(); + } +} diff --git a/src/test/java/org/springframework/data/repository/support/PageableExecutionSupportUnitTests.java b/src/test/java/org/springframework/data/repository/support/PageableExecutionSupportUnitTests.java new file mode 100644 index 000000000..c7a481d9e --- /dev/null +++ b/src/test/java/org/springframework/data/repository/support/PageableExecutionSupportUnitTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2016 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 + * + * http://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.repository.support; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.support.PageableExecutionSupport.TotalSupplier; + +/** + * Unit tests for {@link PageableExecutionSupport}. + * + * @author Mark Paluch + */ +@RunWith(MockitoJUnitRunner.class) +public class PageableExecutionSupportUnitTests { + + @Mock TotalSupplier totalSupplierMock; + + /** + * @see DATAMCNS-884 + */ + @Test + public void firstPageRequestIsLessThanOneFullPageDoesNotRequireTotal() { + + Page page = PageableExecutionSupport.getPage(Arrays.asList(1, 2, 3), new PageRequest(0, 10), + totalSupplierMock); + + assertThat(page, hasItems(1, 2, 3)); + assertThat(page.getTotalElements(), is(3L)); + verifyZeroInteractions(totalSupplierMock); + } + + /** + * @see DATAMCNS-884 + */ + @Test + public void noPageableRequesDoesNotRequireTotal() { + + Page page = PageableExecutionSupport.getPage(Arrays.asList(1, 2, 3), null, totalSupplierMock); + + assertThat(page, hasItems(1, 2, 3)); + assertThat(page.getTotalElements(), is(3L)); + verifyZeroInteractions(totalSupplierMock); + } + + /** + * @see DATAMCNS-884 + */ + @Test + public void subsequentPageRequestIsLessThanOneFullPageDoesNotRequireTotal() { + + Page page = PageableExecutionSupport.getPage(Arrays.asList(1, 2, 3), new PageRequest(5, 10), + totalSupplierMock); + + assertThat(page, hasItems(1, 2, 3)); + assertThat(page.getTotalElements(), is(53L)); + verifyZeroInteractions(totalSupplierMock); + } + + /** + * @see DATAMCNS-884 + */ + @Test + public void firstPageRequestHitsUpperBoundRequiresTotal() { + + when(totalSupplierMock.get()).thenReturn(4L); + + Page page = PageableExecutionSupport.getPage(Arrays.asList(1, 2, 3), new PageRequest(0, 3), + totalSupplierMock); + + assertThat(page, hasItems(1, 2, 3)); + assertThat(page.getTotalElements(), is(4L)); + verify(totalSupplierMock).get(); + } + + /** + * @see DATAMCNS-884 + */ + @Test + public void subsequentPageRequestHitsUpperBoundRequiresTotal() { + + when(totalSupplierMock.get()).thenReturn(7L); + + Page page = PageableExecutionSupport.getPage(Arrays.asList(1, 2, 3), new PageRequest(1, 3), + totalSupplierMock); + + assertThat(page, hasItems(1, 2, 3)); + assertThat(page.getTotalElements(), is(7L)); + verify(totalSupplierMock).get(); + } + + /** + * @see DATAMCNS-884 + */ + @Test + public void subsequentPageRequestWithoutResultRequiresRequireTotal() { + + when(totalSupplierMock.get()).thenReturn(7L); + Page page = PageableExecutionSupport.getPage(Collections. emptyList(), new PageRequest(5, 10), + totalSupplierMock); + + assertThat(page.getTotalElements(), is(7L)); + verify(totalSupplierMock).get(); + } +}