From f98c7b9c93caabadf1a4153ae02aa7464bcc36a3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 2 Nov 2023 08:49:24 +0100 Subject: [PATCH] Show how to use Limit with repository queries. Add methods taking a Limit parameter to various samples. Original pull request: #672 Closes #671 --- .../cassandra/basic/BasicUserRepository.java | 10 ++++++++ .../springdata/cassandra/basic/User.java | 6 +++++ .../basic/BasicUserRepositoryTests.java | 23 +++++++++++++++++++ .../people/ReactivePersonRepository.java | 10 ++++++++ ...activePersonRepositoryIntegrationTest.java | 9 ++++++++ .../jpa/simple/SimpleUserRepository.java | 23 +++++++++++++++++++ .../jpa/simple/SimpleUserRepositoryTests.java | 18 +++++++++++++++ .../mongodb/customer/CustomerRepository.java | 10 ++++++++ .../CustomerRepositoryIntegrationTest.java | 12 ++++++++++ .../people/ReactivePersonRepository.java | 14 +++++++++++ ...activePersonRepositoryIntegrationTest.java | 9 ++++++++ .../r2dbc/basics/CustomerRepository.java | 3 +++ .../TransactionalServiceIntegrationTests.java | 20 ++++++++++++++++ 13 files changed, 167 insertions(+) diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java b/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java index 842610747..a3fc0601b 100644 --- a/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java +++ b/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java @@ -18,6 +18,7 @@ import java.util.List; import org.springframework.data.cassandra.repository.Query; +import org.springframework.data.domain.Limit; import org.springframework.data.repository.CrudRepository; /** @@ -55,4 +56,13 @@ public interface BasicUserRepository extends CrudRepository { * @return */ List findUsersByLastnameStartsWith(String lastnamePrefix); + + /** + * Same as {@link #findUsersByLastnameStartsWith(String)} but reducing the result size to a given {@link Limit}. + * + * @param lastnamePrefix + * @param maxResults the maximum number of results returned. + * @return + */ + List findUsersByLastnameStartsWith(String lastnamePrefix, Limit maxResults); } diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java b/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java index 25df51a0a..96c05b186 100644 --- a/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java +++ b/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java @@ -43,4 +43,10 @@ public class User { public User(Long id) { this.setId(id); } + + public User(Long id, String firstname, String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + } } diff --git a/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java b/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java index 436b37680..8e6fe0bb1 100644 --- a/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java +++ b/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assumptions.*; +import java.util.stream.LongStream; + import example.springdata.cassandra.util.CassandraKeyspace; import example.springdata.cassandra.util.CassandraVersion; @@ -26,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Limit; import org.springframework.data.util.Version; import com.datastax.oss.driver.api.core.CqlSession; @@ -120,4 +123,24 @@ void findByDerivedQueryMethodWithSASI() throws InterruptedException { assertThat(repository.findUsersByLastnameStartsWith("last")).contains(user); } + + /** + * Spring Data Cassandra supports {@code Limit} to reduce the number of returned results. + */ + @Test + void limitResultSize() throws InterruptedException { + + assumeThat(CassandraVersion.getReleaseVersion(session).isGreaterThanOrEqualTo(CASSANDRA_3_4)).isTrue(); + + session.execute("CREATE CUSTOM INDEX ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';"); + /* + Cassandra secondary indexes are created in the background without the possibility to check + whether they are available or not. So we are forced to just wait. *sigh* + */ + Thread.sleep(1000); + + LongStream.range(0, 10).forEach(id -> repository.save(new User(id, user.getFirstname(), user.getLastname()))); + + assertThat(repository.findUsersByLastnameStartsWith("last", Limit.of(5))).hasSize(5); + } } diff --git a/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java b/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java index 6c8a38402..130781be3 100644 --- a/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java +++ b/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java @@ -15,6 +15,7 @@ */ package example.springdata.cassandra.people; +import org.springframework.data.domain.Limit; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -36,6 +37,15 @@ public interface ReactivePersonRepository extends ReactiveCrudRepository findByLastname(String lastname); + /** + * Derived query selecting by {@code lastname} reducing the result size to a given {@link Limit}. + * + * @param lastname + * @param maxResults the maximum number of results returned. + * @return + */ + Flux findByLastname(String lastname, Limit maxResults); + /** * String query selecting one entity. * diff --git a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java index bf17e8897..e441b34af 100644 --- a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java +++ b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java @@ -16,6 +16,7 @@ package example.springdata.cassandra.people; import example.springdata.cassandra.util.CassandraKeyspace; +import org.springframework.data.domain.Limit; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -89,6 +90,14 @@ void shouldQueryDataWithQueryDerivation() { StepVerifier.create(repository.findByLastname("White")).expectNextCount(2).verifyComplete(); } + /** + * Fetch data limiting result size. + */ + @Test + void limitResultSize() { + StepVerifier.create(repository.findByLastname("White", Limit.of(1))).expectNextCount(1).verifyComplete(); + } + /** * Fetch data using a string query. */ diff --git a/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java b/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java index 75bfcc659..9f5c77a85 100644 --- a/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java +++ b/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java @@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; +import org.springframework.data.domain.Limit; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @@ -63,6 +64,17 @@ public interface SimpleUserRepository extends ListCrudRepository { */ List findByLastname(String lastname); + /** + * Find at most the number of users defined via maxResults with the given lastname. + * This method will be translated into a query by constructing it directly from the method name as there is no other + * query declared. + * + * @param lastname + * @param maxResults the maximum number of results returned. + * @return + */ + List findByLastname(String lastname, Limit maxResults); + /** * Returns all users with the given firstname. This method will be translated into a query using the one declared in * the {@link Query} annotation declared one. @@ -73,6 +85,17 @@ public interface SimpleUserRepository extends ListCrudRepository { @Query("select u from User u where u.firstname = :firstname") List findByFirstname(String firstname); + /** + * Returns at most the number of users defined via {@link Limit} with the given firstname. This method will be + * translated into a query using the one declared in the {@link Query} annotation declared one. + * + * @param firstname + * @param maxResults the maximum number of results returned. + * @return + */ + @Query("select u from User u where u.firstname = :firstname") + List findByFirstname(String firstname, Limit maxResults); + /** * Returns all users with the given name as first- or lastname. This makes the query to method relation much more * refactoring-safe as the order of the method parameters is completely irrelevant. diff --git a/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java b/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java index 9c4da22d6..5fcfdc773 100644 --- a/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java +++ b/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java @@ -27,6 +27,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; @@ -36,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Limit; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.transaction.annotation.Propagation; @@ -83,6 +85,22 @@ void findSavedUserByLastname() { assertThat(repository.findByLastname("lastname")).contains(user); } + @Test + void findLimitedNumberOfUsersViaDerivedQuery() { + + IntStream.range(0, 10).forEach($ -> repository.save(new User(user.getFirstname(), user.getLastname()))); + + assertThat(repository.findByLastname("lastname", Limit.of(5))).hasSize(5); + } + + @Test + void findLimitedNumberOfUsersViaAnnotatedQuery() { + + IntStream.range(0, 10).forEach($ -> repository.save(new User(user.getFirstname(), user.getLastname()))); + + assertThat(repository.findByFirstname(user.getFirstname(), Limit.of(5))).hasSize(5); + } + @Test void findByFirstnameOrLastname() { diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java index ef239160b..8a94fa5ef 100644 --- a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java +++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.stream.Stream; +import org.springframework.data.domain.Limit; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResults; @@ -41,6 +42,15 @@ public interface CustomerRepository extends CrudRepository { */ List findByLastname(String lastname, Sort sort); + /** + * Derived query reducing result size to a given {@link Limit}. + * + * @param lastname + * @param maxResults the maximum number of results returned. + * @return + */ + List findByLastname(String lastname, Limit maxResults); + /** * Showcase for a repository query using geospatial functionality. * diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java index 2c2352d3f..5a3e76dd6 100644 --- a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java +++ b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; +import org.springframework.data.domain.Limit; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; @@ -96,6 +97,17 @@ void findCustomersUsingQuerydslSort() { assertThat(result.get(1)).isEqualTo(oliver); } + /** + * Test case to show how to reduce result size with dynamic {@link Limit}. + */ + @Test + void limitResultSize() { + + var result = repository.findByLastname("Matthews", Limit.of(1)); + + assertThat(result).hasSize(1); + } + /** * Test case to show the usage of Java {@link Stream}. */ diff --git a/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java b/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java index adc63a317..1004a90e0 100644 --- a/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java +++ b/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java @@ -15,6 +15,9 @@ */ package example.springdata.mongodb.people; +import java.util.List; + +import org.springframework.data.domain.Limit; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -37,6 +40,17 @@ public interface ReactivePersonRepository extends ReactiveCrudRepository findByLastname(String lastname); + /** + * Find at most the number of users defined via maxResults with the given lastname. + * This method will be translated into a query by constructing it directly from the method name as there is no other + * query declared. + * + * @param lastname + * @param maxResults the maximum number of results returned. + * @return + */ + Flux findByLastname(String lastname, Limit maxResults); + /** * String query selecting one entity. * diff --git a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java index 7224a1f1d..194659b47 100644 --- a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java +++ b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import example.springdata.mongodb.util.MongoContainers; +import org.springframework.data.domain.Limit; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -157,6 +158,14 @@ void shouldQueryDataWithQueryDerivation() { repository.findByLastname("White").as(StepVerifier::create).expectNextCount(2).verifyComplete(); } + /** + * Limit result size. + */ + @Test + void shouldLimitResultSize() { + repository.findByLastname("White", Limit.of(1)).as(StepVerifier::create).expectNextCount(1).verifyComplete(); + } + /** * Fetch data using a string query. */ diff --git a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java index bbf002b6b..5358fb499 100644 --- a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java +++ b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java @@ -15,6 +15,7 @@ */ package example.springdata.r2dbc.basics; +import org.springframework.data.domain.Limit; import reactor.core.publisher.Flux; import org.springframework.data.r2dbc.repository.Query; @@ -28,4 +29,6 @@ interface CustomerRepository extends ReactiveCrudRepository { @Query("select id, firstname, lastname from customer c where c.lastname = :lastname") Flux findByLastname(String lastname); + + Flux findByLastname(String lastname, Limit limit); } diff --git a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java index f3fe9869a..6b788a337 100644 --- a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java +++ b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java @@ -15,6 +15,7 @@ */ package example.springdata.r2dbc.basics; +import org.springframework.data.domain.Limit; import reactor.core.publisher.Hooks; import reactor.test.StepVerifier; @@ -72,6 +73,25 @@ void exceptionTriggersRollback() { .verifyComplete(); } + @Test + void limitResultSize() { + + service.save(new Customer(null, "Carter", "Matthews")) // + .as(StepVerifier::create) // + .expectNextMatches(Customer::hasId) // + .verifyComplete(); + + service.save(new Customer(null, "Evad", "Matthews")) // + .as(StepVerifier::create) // + .expectNextMatches(Customer::hasId) // + .verifyComplete(); + + repository.findByLastname("Matthews", Limit.of(1)) // + .as(StepVerifier::create) // + .expectNextCount(1) + .verifyComplete(); + } + @Test // #500 void insertsDataTransactionally() {