Skip to content

Commit 4718c45

Browse files
committed
Introduce public ReactivePageableExecutionUtils variant of pageable utils.
Closes spring-projects#3209
1 parent 771347b commit 4718c45

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2021-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.support;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import java.util.List;
21+
22+
import org.springframework.data.domain.Page;
23+
import org.springframework.data.domain.PageImpl;
24+
import org.springframework.data.domain.Pageable;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* Support for query execution using {@link Pageable}. Using {@link ReactivePageableExecutionUtils} assumes that data
29+
* queries are cheaper than {@code COUNT} queries and so some cases can take advantage of optimizations.
30+
*
31+
* @author Mark Paluch
32+
* @since 3.5
33+
*/
34+
public abstract class ReactivePageableExecutionUtils {
35+
36+
private ReactivePageableExecutionUtils() {}
37+
38+
/**
39+
* Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link Mono} applying
40+
* optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the
41+
* result size and {@link Pageable}.
42+
*
43+
* @param content must not be {@literal null}.
44+
* @param pageable must not be {@literal null}.
45+
* @param totalSupplier must not be {@literal null}.
46+
* @return the {@link Page}.
47+
*/
48+
public static <T> Mono<Page<T>> getPage(List<T> content, Pageable pageable, Mono<Long> totalSupplier) {
49+
50+
Assert.notNull(content, "Content must not be null");
51+
Assert.notNull(pageable, "Pageable must not be null");
52+
Assert.notNull(totalSupplier, "TotalSupplier must not be null");
53+
54+
if (pageable.isUnpaged() || pageable.getOffset() == 0) {
55+
56+
if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
57+
return Mono.just(new PageImpl<>(content, pageable, content.size()));
58+
}
59+
60+
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
61+
}
62+
63+
if (!content.isEmpty() && pageable.getPageSize() > content.size()) {
64+
return Mono.just(new PageImpl<>(content, pageable, pageable.getOffset() + content.size()));
65+
}
66+
67+
return totalSupplier.map(total -> new PageImpl<>(content, pageable, total));
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2020-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.support;
17+
18+
import static java.util.Collections.*;
19+
import static org.assertj.core.api.Assertions.*;
20+
import static org.mockito.Mockito.*;
21+
22+
import reactor.core.publisher.Mono;
23+
24+
import java.util.Arrays;
25+
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.extension.ExtendWith;
28+
import org.mockito.junit.jupiter.MockitoExtension;
29+
30+
import org.springframework.data.domain.PageRequest;
31+
import org.springframework.data.domain.Pageable;
32+
33+
/**
34+
* Unit tests for {@link ReactivePageableExecutionUtils}.
35+
*
36+
* @author Mark Paluch
37+
*/
38+
@SuppressWarnings("unchecked")
39+
@ExtendWith(MockitoExtension.class)
40+
class ReactivePageableExecutionUtilsUnitTests {
41+
42+
@Test // DATAMCNS-884, GH-3209
43+
void firstPageRequestIsLessThanOneFullPageDoesNotRequireTotal() {
44+
45+
Mono<Long> totalSupplierMock = mock(Mono.class);
46+
var page = ReactivePageableExecutionUtils.getPage(Arrays.asList(1, 2, 3), PageRequest.of(0, 10), totalSupplierMock)
47+
.block();
48+
49+
assertThat(page).contains(1, 2, 3);
50+
assertThat(page.getTotalElements()).isEqualTo(3L);
51+
verifyNoInteractions(totalSupplierMock);
52+
}
53+
54+
@Test // DATAMCNS-884, GH-3209
55+
void noPageableRequestDoesNotRequireTotal() {
56+
57+
Mono<Long> totalSupplierMock = mock(Mono.class);
58+
var page = ReactivePageableExecutionUtils.getPage(Arrays.asList(1, 2, 3), Pageable.unpaged(), totalSupplierMock)
59+
.block();
60+
61+
assertThat(page).contains(1, 2, 3);
62+
assertThat(page.getTotalElements()).isEqualTo(3L);
63+
64+
verifyNoInteractions(totalSupplierMock);
65+
}
66+
67+
@Test // DATAMCNS-884, GH-3209
68+
void subsequentPageRequestIsLessThanOneFullPageDoesNotRequireTotal() {
69+
70+
Mono<Long> totalSupplierMock = mock(Mono.class);
71+
var page = ReactivePageableExecutionUtils.getPage(Arrays.asList(1, 2, 3), PageRequest.of(5, 10), totalSupplierMock)
72+
.block();
73+
74+
assertThat(page).contains(1, 2, 3);
75+
assertThat(page.getTotalElements()).isEqualTo(53L);
76+
77+
verifyNoInteractions(totalSupplierMock);
78+
}
79+
80+
@Test // DATAMCNS-884, GH-3209
81+
void firstPageRequestHitsUpperBoundRequiresTotal() {
82+
83+
Mono<Long> totalSupplierMock = Mono.just(4L);
84+
85+
var page = ReactivePageableExecutionUtils.getPage(Arrays.asList(1, 2, 3), PageRequest.of(0, 3), totalSupplierMock)
86+
.block();
87+
88+
assertThat(page).contains(1, 2, 3);
89+
assertThat(page.getTotalElements()).isEqualTo(4L);
90+
}
91+
92+
@Test // DATAMCNS-884, GH-3209
93+
void subsequentPageRequestHitsUpperBoundRequiresTotal() {
94+
95+
Mono<Long> totalSupplierMock = Mono.just(7L);
96+
97+
var page = ReactivePageableExecutionUtils.getPage(Arrays.asList(1, 2, 3), PageRequest.of(1, 3), totalSupplierMock)
98+
.block();
99+
100+
assertThat(page).contains(1, 2, 3);
101+
assertThat(page.getTotalElements()).isEqualTo(7L);
102+
}
103+
104+
@Test // DATAMCNS-884, GH-3209
105+
void subsequentPageRequestWithoutResultRequiresRequireTotal() {
106+
107+
Mono<Long> totalSupplierMock = Mono.just(7L);
108+
var page = ReactivePageableExecutionUtils.getPage(emptyList(), PageRequest.of(5, 10), totalSupplierMock).block();
109+
110+
assertThat(page.getTotalElements()).isEqualTo(7L);
111+
}
112+
}

0 commit comments

Comments
 (0)