WebFlux的探索与实践 - r2dbc的分页查询
自从上次立下这系列的FLAG之后就再也不想碰了。今天难得早起出门面试,回家之后突发奇想打算再写点儿什么敷衍一下,于是便有了这篇文章。
前言
虽然响应式API更加适合流式列表的查询,但是分页这东西可是很常见的。
也没什么前言可说,反正就是一篇介绍如何在 Spring WebFlux 中使用 Spring Data R2DBC 进行分页查询的文章。如果喜欢,还望点个赞喵~
文章会从创建项目开始,你要是没啥兴趣,就往下划划。
准备
总而言之,先创建个项目,并且要加上 WebFlux
、R2DBC
和一个支持 R2DBC
的数据库驱动。
至于驱动的选择,你可以去 R2DBC官方网站的这里 看看。
你可以去 start.spring.io 去整个项目下来,我选择使用 gradle
构建项目,
这里是我的项目配置:
gradle.build.kts
plugins {
java
id("org.springframework.boot") version "3.0.3"
id("io.spring.dependency-management") version "1.1.0"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly("org.projectlombok:lombok")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-webflux")
runtimeOnly("com.h2database:h2")
runtimeOnly("io.r2dbc:r2dbc-h2")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
testRuntimeOnly("com.h2database:h2")
testRuntimeOnly("io.r2dbc:r2dbc-h2")
}
tasks.withType<Test> {
useJUnitPlatform()
}
这里我选择使用 H2
数据库作为演示用的数据库,其他内容一律默认。
实体类与Repository
按照惯例,首先建个表用作示例:
schema.sql
DROP TABLE IF EXISTS foo;
CREATE TABLE IF NOT EXISTS foo(
id int auto_increment not null primary key comment 'id',
name varchar(20) not null default '' comment '名称',
size int not null default 0 comment '大小'
)
data.sql
INSERT INTO foo(name, size) VALUES ('name1', 1);
INSERT INTO foo(name, size) VALUES ('name2', 2);
INSERT INTO foo(name, size) VALUES ('name3', 3);
INSERT INTO foo(name, size) VALUES ('name4', 4);
INSERT INTO foo(name, size) VALUES ('name5', 114);
INSERT INTO foo(name, size) VALUES ('name6', 514);
INSERT INTO foo(name, size) VALUES ('name7', 19);
INSERT INTO foo(name, size) VALUES ('name8', 10);
INSERT INTO foo(name, size) VALUES ('name9', 11);
INSERT INTO foo(name, size) VALUES ('name10', 12);
INSERT INTO foo(name, size) VALUES ('name11', 13);
INSERT INTO foo(name, size) VALUES ('name12', 14);
INSERT INTO foo(name, size) VALUES ('name13', 15);
INSERT INTO foo(name, size) VALUES ('name14', 16);
INSERT INTO foo(name, size) VALUES ('name15', 17);
foo
是什么意思呢?我也不清楚,但是反正我们这次要去分页查询这个 foo
的表。
接下来,整个对应的实体类吧:
/**
* 数据库 foo 对应实体类
*
* @param id 主键
* @param name 名称
* @param size 大小
*/
public record Foo(@Id Integer id, String name, Integer size) {
}
然后给这个实体类提供一个对应的 Repository
实现。或者更准确的说,是 ReactiveRepository
的实现:
/**
* {@link Foo} 的 Repository 实现
* @author ForteScarlet
*/
@Repository
public interface FooRepository extends R2dbcRepository<Foo, Integer> {
}
顺带一提,R2dbcRepository<T, ID>
实现了下述三个基础接口:
ReactiveCrudRepository<T, ID>
ReactiveSortingRepository<T, ID>
ReactiveQueryByExampleExecutor<T>
那么这样就完成了吗?并没有。通常情况下,一个最简化的、整体性的分页数据应该包括 数据总量
和 分页数据列表
这两个信息,那么让我们首先来提供一个 Paged
类型:
/**
* 分页数据体
*
* @param total 数据总量
* @param data 数据列表
*/
public record Paged<T>(long total, List<T> data) {
}
接下来,因为我们之前的 FooRepository
中已经包含了查询数据总量的 count
,所以接下来我们只需要一个查询分页列表数据的方法就好了。十分幸运,R2DBC Repositories 的 Query Methods
支持我们直接这么写:
@Repository
public interface FooRepository extends R2dbcRepository<Foo, Integer> {
/**
* 分页查询 foo
* @param pageable 分页信息
* @return paged foo flux
*/
Flux<Foo> findAllBy(Pageable pageable);
}
直接在接口中增加一个如上所示的 findAllBy
并提供一个分页参数即可。当然,因为我们在用 r2dbc
,所以返回值应该是响应式的 Flux
类型。
这里的 Pageable
是Spring所提供的类型,所以可以直接拿来用。
接下来让我们来试试效果。先查询总数,再查询列表,然后将他们合并为一个 Paged
:
@SpringBootTest
class WebfluxR2dbcPageableDemoApplicationTests {
@Test
void pagedTest(@Autowired FooRepository repository) {
// 第一页的三条数据
var paged = PageRequest.of(0, 2);
repository.count().flatMap(total -> repository
.findAllBy(paged)
.collectList()
.map(list -> new Paged<>(total, list)))
.as(StepVerifier::create)
.consumeNextWith(System.out::println) // 控制台输出
.verifyComplete();
}
}
输出:
Paged[total=15, data=[Foo[id=1, name=name1, size=1], Foo[id=2, name=name2, size=2]]]
在这个单元测试中,我们首先准备了一个代表 第一页的三条数据
的分页信息。其中,PageRequest
是Spring提供的 Pageable
的一个基本的实现类,所以直接借来用了。
我们首先通过 repository.count
查询数据库数据总数 Mono<Integer>
, 再通过 flatMap
进行下一步,也就是查询列表。
查询列表使用了我们之前的 findAllBy(Pageable)
,然后使用 collectList
将其收集为一个 Mono<List<Foo>>
。