JAVA-24463 Coalesce spring-reactive-data and spring-reactive-data-2 into a single module (#15026)

Co-authored-by: timis1 <noreplay@yahoo.com>
This commit is contained in:
timis1
2023-10-20 18:23:11 +03:00
committed by GitHub
parent ab85c88147
commit 20c2fc3bb5
15 changed files with 39 additions and 120 deletions
@@ -3,7 +3,9 @@
This module contains articles about reactive Spring Boot Data
### The Course
The "REST With Spring" Classes: http://bit.ly/restwithspring
### Relevant Articles
- [A Quick Look at R2DBC with Spring Data](https://www.baeldung.com/spring-data-r2dbc)
- [A Quick Look at R2DBC with Spring Data](https://www.baeldung.com/spring-data-r2dbc)
- [Pagination in Spring Webflux and Spring Data Reactive](https://www.baeldung.com/spring-data-webflux-pagination)
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
<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-reactive-data</artifactId>
<name>spring-reactive-data</name>
<name>spring-reactive-data-2</name>
<packaging>jar</packaging>
<parent>
@@ -13,17 +13,9 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
@@ -31,13 +23,16 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
<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>
@@ -45,12 +40,29 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<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>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>
<dependency>
<groupId>io.r2dbc</groupId>
@@ -58,17 +70,4 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<properties>
<log4j2.version>2.17.1</log4j2.version>
</properties>
</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));
}
}
@@ -1,12 +1,10 @@
package com.baeldung;
package com.baeldung.r2dbc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.r2dbc.R2dbcApplication;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = R2dbcApplication.class)
public class SpringContextTest {