BAEL-6584: Added spring data reactive pagination along with tests (#14478)
* BAEL-6584: Added spring data reactive pagination along with tests * BAEL-6584: removed unnecessary spaces --------- Co-authored-by: balasr3 <balamurugan.radhakrishnan@imgarena.com>
This commit is contained in:
parent
891652322d
commit
787ed13597
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>spring-5-data-reactive</module>
|
<module>spring-5-data-reactive</module>
|
||||||
|
<module>spring-5-data-reactive-2</module>
|
||||||
<module>spring-5-reactive</module>
|
<module>spring-5-reactive</module>
|
||||||
<module>spring-5-reactive-2</module>
|
<module>spring-5-reactive-2</module>
|
||||||
<module>spring-5-reactive-3</module>
|
<module>spring-5-reactive-3</module>
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
## Spring Data Reactive Project
|
||||||
|
|
||||||
|
This module contains articles about reactive Spring 5 Data
|
||||||
|
|
||||||
|
### The Course
|
||||||
|
|
||||||
|
The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||||
|
|
||||||
|
### Relevant Articles
|
77
spring-reactive-modules/spring-5-data-reactive-2/pom.xml
Normal file
77
spring-reactive-modules/spring-5-data-reactive-2/pom.xml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-5-data-reactive-2</artifactId>
|
||||||
|
<name>spring-5-data-reactive-2</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring.reactive</groupId>
|
||||||
|
<artifactId>spring-reactive-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.r2dbc</groupId>
|
||||||
|
<artifactId>r2dbc-h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
<version>2.0.1.Final</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.baeldung.pagination;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class PaginationApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(PaginationApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.baeldung.pagination.config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PageRequest defaultPageRequest() {
|
||||||
|
return PageRequest.of(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||||
|
SortHandlerMethodArgumentResolver argumentResolver = new SortHandlerMethodArgumentResolver();
|
||||||
|
argumentResolver.setSortParameter("sort");
|
||||||
|
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(argumentResolver);
|
||||||
|
resolver.setFallbackPageable(defaultPageRequest());
|
||||||
|
resolver.setPageParameterName("page");
|
||||||
|
resolver.setSizeParameterName("size");
|
||||||
|
argumentResolvers.add(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.baeldung.pagination.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator;
|
||||||
|
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||||
|
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||||
|
|
||||||
|
import io.r2dbc.spi.ConnectionFactory;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class DatabaseConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
|
||||||
|
|
||||||
|
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||||
|
initializer.setConnectionFactory(connectionFactory);
|
||||||
|
|
||||||
|
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||||
|
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("init.sql")));
|
||||||
|
initializer.setDatabasePopulator(populator);
|
||||||
|
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.baeldung.pagination.controller;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.pagination.model.Product;
|
||||||
|
import com.baeldung.pagination.repository.ProductRepository;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProductPaginationController {
|
||||||
|
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
@GetMapping("/products")
|
||||||
|
public Mono<Page<Product>> findAllProducts(Pageable pageable) {
|
||||||
|
return this.productRepository.findAllBy(pageable)
|
||||||
|
.collectList()
|
||||||
|
.zipWith(this.productRepository.count())
|
||||||
|
.map(p -> new PageImpl<>(p.getT1(), pageable, p.getT2()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.baeldung.pagination.model;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.relational.core.mapping.Table;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Table
|
||||||
|
public class Product {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Getter
|
||||||
|
private UUID id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(max = 255, message = "The property 'name' must be less than or equal to 255 characters.")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private double price;
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.pagination.repository;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import com.baeldung.pagination.model.Product;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ProductRepository extends ReactiveSortingRepository<Product, UUID> {
|
||||||
|
Flux<Product> findAllBy(Pageable pageable);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
create table product
|
||||||
|
(
|
||||||
|
id UUID DEFAULT RANDOM_UUID() PRIMARY KEY,
|
||||||
|
name varchar(50),
|
||||||
|
price decimal
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into product(name, price)
|
||||||
|
values ('product_A', 1.0);
|
||||||
|
insert into product(name, price)
|
||||||
|
values ('product_B', 2.0);
|
||||||
|
insert into product(name, price)
|
||||||
|
values ('product_C', 3.0);
|
||||||
|
insert into product(name, price)
|
||||||
|
values ('product_D', 4.0);
|
@ -0,0 +1,122 @@
|
|||||||
|
package com.baeldung.pagination.controller;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.atIndex;
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
|
||||||
|
import com.baeldung.pagination.model.Product;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@AutoConfigureWebTestClient
|
||||||
|
class ProductPaginationControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebTestClient webClient;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void WhenProductEndpointIsHit_thenShouldReturnProductsWithPagination() throws JsonProcessingException {
|
||||||
|
String response = webClient.get()
|
||||||
|
.uri("/products")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.is2xxSuccessful()
|
||||||
|
.expectBody(String.class)
|
||||||
|
.returnResult()
|
||||||
|
.getResponseBody();
|
||||||
|
Assertions.assertNotNull(response);
|
||||||
|
|
||||||
|
JsonNode pageResponse = new ObjectMapper().readValue(response, JsonNode.class);
|
||||||
|
Assertions.assertNotNull(pageResponse);
|
||||||
|
Assertions.assertEquals(4, pageResponse.get("totalElements")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(1, pageResponse.get("totalPages")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertTrue(pageResponse.get("last")
|
||||||
|
.asBoolean());
|
||||||
|
Assertions.assertTrue(pageResponse.get("first")
|
||||||
|
.asBoolean());
|
||||||
|
Assertions.assertEquals(100, pageResponse.get("size")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(4, pageResponse.get("numberOfElements")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(0, pageResponse.get("pageable")
|
||||||
|
.get("offset")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(0, pageResponse.get("pageable")
|
||||||
|
.get("pageNumber")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(100, pageResponse.get("pageable")
|
||||||
|
.get("pageSize")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertTrue(pageResponse.get("pageable")
|
||||||
|
.get("paged")
|
||||||
|
.asBoolean());
|
||||||
|
List<Product> content = new ObjectMapper().readValue(String.valueOf(pageResponse.get("content")), new TypeReference<List<Product>>() {
|
||||||
|
});
|
||||||
|
assertThat(content).hasSize(4);
|
||||||
|
assertThat(content).extracting("name", "price")
|
||||||
|
.contains(tuple("product_A", 1.0), atIndex(0))
|
||||||
|
.contains(tuple("product_B", 2.0), atIndex(1))
|
||||||
|
.contains(tuple("product_C", 3.0), atIndex(2))
|
||||||
|
.contains(tuple("product_D", 4.0), atIndex(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void WhenProductEndpointIsHitWithPageSizeAs2AndSortPriceByDesc_thenShouldReturnProductsWithPaginationIgnoring2Products() throws JsonProcessingException {
|
||||||
|
String response = webClient.get()
|
||||||
|
.uri("/products?page=1&size=2&sort=price,DESC")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.is2xxSuccessful()
|
||||||
|
.expectBody(String.class)
|
||||||
|
.returnResult()
|
||||||
|
.getResponseBody();
|
||||||
|
Assertions.assertNotNull(response);
|
||||||
|
|
||||||
|
JsonNode pageResponse = new ObjectMapper().readValue(response, JsonNode.class);
|
||||||
|
Assertions.assertNotNull(pageResponse);
|
||||||
|
Assertions.assertEquals(4, pageResponse.get("totalElements")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(2, pageResponse.get("totalPages")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertTrue(pageResponse.get("last")
|
||||||
|
.asBoolean());
|
||||||
|
Assertions.assertFalse(pageResponse.get("first")
|
||||||
|
.asBoolean());
|
||||||
|
Assertions.assertEquals(2, pageResponse.get("size")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(2, pageResponse.get("numberOfElements")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(2, pageResponse.get("pageable")
|
||||||
|
.get("offset")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(1, pageResponse.get("pageable")
|
||||||
|
.get("pageNumber")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertEquals(2, pageResponse.get("pageable")
|
||||||
|
.get("pageSize")
|
||||||
|
.asInt());
|
||||||
|
Assertions.assertTrue(pageResponse.get("pageable")
|
||||||
|
.get("paged")
|
||||||
|
.asBoolean());
|
||||||
|
List<Product> content = new ObjectMapper().readValue(String.valueOf(pageResponse.get("content")), new TypeReference<List<Product>>() {
|
||||||
|
});
|
||||||
|
assertThat(content).hasSize(2);
|
||||||
|
assertThat(content).extracting("name", "price")
|
||||||
|
.contains(tuple("product_B", 2.0), atIndex(0))
|
||||||
|
.contains(tuple("product_A", 1.0), atIndex(1));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user