WCDK R2DBC 是一个基于 Spring Boot、Spring Data R2DBC 和 Project Reactor 的响应式数据库访问框架,面向 WebFlux 微服务和响应式业务场景。
WCDK R2DBC 是一个面向 Spring Boot 3.5+、Spring Data R2DBC 和 Project Reactor 的响应式数据库访问框架,适用于 Spring WebFlux 微服务和响应式业务系统。它提供 Repository 动态代理、响应式 CRUD、派生查询、QueryWrapper、事务、多数据源、逻辑删除、XML SQL 以及达梦、PostgreSQL、MySQL 和 Oracle 数据库方言支持。
WCDK R2DBC is a reactive database access framework for Spring Boot 3.5+, Spring Data R2DBC, and Project Reactor. It is designed for Spring WebFlux microservices and reactive applications, providing dynamic Repository proxies, reactive CRUD, derived queries, QueryWrapper, transactions, multi-datasource routing, logical deletion, XML SQL, and dialect support for Dameng, PostgreSQL, MySQL, and Oracle.
- 保持全链路响应式:Repository、Service 和 Controller 统一使用
Mono/Flux,适配 WebFlux,避免阻塞式 JDBC 编程模型。 - 减少样板代码:通过 Repository 动态代理、标准 CRUD 和派生查询方法,快速构建数据访问层。
- 兼顾易用性与控制力:简单查询使用方法名约定和 QueryWrapper,复杂场景使用 XML SQL 和自定义生命周期拦截器。
- 面向企业数据库场景:内置事务、多数据源、逻辑删除、连接池、SQL 观测和多数据库方言支持。
- 渐进式接入:可以从基础 Repository 开始使用,按需启用分页、Lambda Wrapper、XML SQL、事务和观测能力。
- End-to-end reactive programming: Keep
Mono/Fluxacross Repository, Service, and Controller layers for Spring WebFlux applications. - Less boilerplate: Build the data access layer quickly with dynamic Repository proxies, standard CRUD operations, and derived query methods.
- Simple for common cases, flexible for complex cases: Use method-name conventions and QueryWrapper for common queries, and XML SQL or lifecycle interceptors when deeper control is needed.
- Built for enterprise database scenarios: Includes transactions, multi-datasource routing, logical deletion, connection pooling, SQL observability, and dialect support for multiple databases.
- Adopt incrementally: Start with the basic Repository API and enable pagination, Lambda Wrapper, XML SQL, transactions, or observability as needed.
- 特性
- 环境要求
- 快速开始
- 配置
- Repository API
- @Transient 字段
- 派生查询方法
- QueryWrapper
- Lambda Wrapper
- 分页
- 逻辑删除
- 多数据源
- 事务
- XML SQL
- SQL 生命周期与观测
- 数据库支持
- 响应式使用约束
- 项目结构
- 响应式 API:仓储查询统一返回
Mono/Flux,适配 Spring WebFlux。 - Repository 动态代理:启动时扫描接口并生成仓储代理,业务代码只需定义接口。
- 标准 CRUD:提供新增、按 ID 查询、更新、删除、列表、单条、统计和存在性查询。
- 派生方法:支持
findBy、countBy、existsBy、deleteBy、update...By...等方法名约定。 - 查询构造器:支持字符串字段和 Lambda 属性引用,覆盖等值、范围、模糊、集合、空值、嵌套
AND/OR、排序、分页。 - 逻辑删除:查询、统计、更新和派生删除默认过滤已删除数据。
- 多数据源路由:支持通过
@R2dbcDataSource和 Reactor Context 切换数据源。 - 响应式事务:提供
TransactionalOperator、模板事务和手动事务能力。 - XML SQL:支持 XML 语句、动态 SQL、结果类型和结果映射。
- SQL 生命周期:支持 SQL 执行前后拦截、审计、性能统计和 Micrometer 观测。
- 数据库方言:支持达梦、PostgreSQL、MySQL 和 Oracle 的方言适配。
- 连接池:基于
r2dbc-pool管理连接池参数。 - 类型映射:支持实体、记录类、枚举、Java 时间类型以及自定义值转换器。
- Java 21+
- Spring Boot 3.5+
- Maven 3.9+
- 对应数据库的 R2DBC 驱动
<dependency>
<groupId>com.wcdk.r2dbc</groupId>
<artifactId>wcdk-r2dbc</artifactId>
<version>3.5.16</version>
</dependency>
<!-- 按实际数据库选择一个驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<version>1.0.7.RELEASE</version>
<scope>runtime</scope>
</dependency>项目已经依赖 spring-boot-starter-data-r2dbc 时无需重复声明;达梦、MySQL、Oracle 驱动版本可参考项目 pom.xml 的 profile 配置。
spring:
r2dbc:
url: r2dbc:postgresql://localhost:5432/demo
username: postgres
password: postgres
wcdk:
r2dbc:
enabled: true
sql-log-enabled: true- /demo 不会被达梦 R2DBC URL 识别为 schema。
- 推荐使用 r2dbc:dm://127.0.0.1:5236。
- 或者添加 ?schema=demo
- 优先使用 demo 用户登录。
- 其他用户登录时,每条物理连接需执行 SET SCHEMA demo。
- 提供 CURRENT_USER、CURRENT_SCHEMA 验证 SQL。
package com.example;
import com.wcdk.r2dbc.config.EnableWcdkR2dbcRepositories;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableWcdkR2dbcRepositories(basePackages = "com.example.repository")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}package com.example.repository;
import com.wcdk.r2dbc.annotation.Repository;
import com.wcdk.r2dbc.repository.BaseRepository;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import reactor.core.publisher.Flux;
@Table("sys_user")
public record User(
@Id Long id,
@Column("user_name") String userName,
String email,
Integer status
) {
}
@Repository
public interface UserRepository extends BaseRepository<User> {
Flux<User> findByStatus(Integer status);
}
com.wcdk.r2dbc.Repository仍作为仓储注解的兼容入口保留;基础仓储接口的推荐路径为com.wcdk.r2dbc.repository.BaseRepository。
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public Mono<User> findById(Long id) {
return userRepository.selectById(id);
}
public Flux<User> findEnabledUsers() {
return userRepository.findByStatus(1);
}
public Mono<User> create(User user) {
return userRepository.insert(user);
}
}Controller、Service 和 Repository 之间应保持 Mono / Flux 链路,不要调用 block() 或在业务方法中手动 subscribe()。
完整示例见 application-example.yml。
| 配置项 | 默认值 | 说明 |
|---|---|---|
wcdk.r2dbc.enabled |
false |
是否启用 WCDK R2DBC |
wcdk.r2dbc.sql-log-enabled |
true |
是否输出 SQL 日志 |
wcdk.r2dbc.observability-enabled |
false |
是否启用 Micrometer 观测 |
wcdk.r2dbc.snowflake-id |
false |
是否启用雪花 ID 生成 |
wcdk.r2dbc.quote-identifier |
true |
是否引用数据库标识符;仓储按连接方言生成,MySQL使用反引号,PostgreSQL/Oracle/达梦使用双引号 |
wcdk.r2dbc.mapper-locations |
classpath*:repository/**/*.xml |
XML Mapper 扫描位置 |
wcdk.r2dbc.logic-delete-field |
delFlg |
逻辑删除字段 |
wcdk.r2dbc.logic-not-delete-value |
0 |
未删除值 |
wcdk.r2dbc.logic-delete-value |
1 |
已删除值 |
事务切面默认关闭。如需使用 @Transactional 的 WCDK 响应式事务切面,可显式开启:
wcdk:
r2dbc:
transaction:
aspect-enabled: true数据库初始化配置位于 wcdk.r2dbc.database-initializer,支持 enabled、sql-location、database-type、mode、ignore-errors 和 execute-in-transaction。
推荐导入:
import com.wcdk.r2dbc.annotation.Repository;
import com.wcdk.r2dbc.repository.BaseRepository;| 方法 | 返回值 | 说明 |
|---|---|---|
insert(entity) |
Mono<T> |
插入并返回实体 |
deleteById(id) |
Mono<Long> |
按 ID 删除,返回影响行数 |
updateById(entity) |
Mono<Long> |
按 ID 更新,返回影响行数 |
selectById(id) |
Mono<T> |
按 ID 查询 |
findAll() |
Flux<T> |
查询全部数据 |
selectList(wrapper) |
Flux<T> |
条件查询列表 |
selectOne(wrapper) |
Mono<T> |
查询单条数据 |
selectCount(wrapper) |
Mono<Long> |
条件统计 |
exists(wrapper) |
Mono<Boolean> |
判断数据是否存在 |
selectPage(pageable, wrapper) |
Mono<Page<T>> |
分页查询 |
更新和删除方法支持 Mono<Long>、Mono<Integer>、Mono<Boolean>、Mono<Void> 等兼容返回形式,具体以方法声明为准。
实体中的 @Transient 应使用 org.springframework.data.annotation.Transient。这类字段不会被当作数据表列参与实体元数据解析,因此不会出现在自动生成的 INSERT、UPDATE 以及其他基于实体字段生成的 SQL 中。
@Transient 不会阻止查询结果映射:如果 SELECT 结果包含与该字段匹配的列(字段名按默认规则转换,或使用 @Column 指定列名),RowMapper 会在实体创建完成后填充该字段。查询结果不包含该列时,字段保持默认值。
@Table("sys_user")
public class User {
@Id
private Long id;
@Column("user_name")
private String userName;
@Transient
@Column("display_label")
private String displayLabel;
}例如,XML SQL 或自定义 SELECT 可以返回计算列:
SELECT id, user_name, CONCAT(user_name, ' (active)') AS display_label
FROM sys_user使用持久化构造器时,@Transient 字段不需要作为构造器参数;框架会先完成构造器映射,再根据查询结果补充该字段。@Transient 只影响持久化字段识别,不会自动生成计算列,SQL 仍需显式返回对应列。
框架在仓储代理创建阶段解析方法名,并生成对应的执行计划。常用形式如下:
public interface UserRepository extends BaseRepository<User> {
Flux<User> findByStatus(Integer status);
Flux<User> findByStatusAndEmail(Integer status, String email);
Flux<User> findByStatusOrEmail(Integer status, String email);
Mono<Long> countByStatus(Integer status);
Mono<Boolean> existsByEmail(String email);
Mono<Long> deleteByStatus(Integer status);
Mono<Integer> updateUserNameById(String userName, Long id);
}支持的常见操作包括:
findBy、countBy、existsBy、deleteByupdateXxxById、updateXxxByFieldAnd、Or、OrderByEquals、Not、Like、StartingWith、EndingWithGreaterThan、GreaterThanEqual、LessThan、LessThanEqualIn、NotIn、IsNull、IsNotNull
方法名中的字段必须能映射到实体属性;方法参数数量和返回类型不符合约定时,应在应用启动阶段修正,而不是在请求链路中兜底。
当前 QueryWrapper 位于 com.wcdk.r2dbc.query,推荐使用条件表达式 API:
import com.wcdk.r2dbc.query.QueryWrapper;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1)
.and(nested -> nested
.like("email", "%@example.com")
.or(or -> or.isNull("user_name")
.eq("user_name", "管理员")))
.orderByDesc("id")
.limit(20)
.offset(0);
Flux<User> users = userRepository.selectList(wrapper);常用方法:
eq、ne、gt、ge、lt、le、like、in、inArray、notIn、notInArray、isNull、isNotNull、and、or、orderByAsc、orderByDesc、limit、offset、page。
and / or 会先生成条件表达式树,再渲染为 SQL。连续普通条件默认使用 AND,
同一种连续逻辑会合并;混用 AND / OR 时会保留括号来维持优先级。以下示例仅展示
WHERE 片段,实际执行时字段会按实体元数据映射为数据库列名:
| QueryWrapper 写法 | 生成的 WHERE 片段 |
|---|---|
new QueryWrapper<>() |
空字符串 |
.and(n -> {}) |
空字符串 |
.or(n -> {}) |
空字符串 |
.and(n -> n.eq("a", 1)) |
WHERE a = :p0 |
.or(n -> n.eq("a", 1)) |
WHERE a = :p0 |
.eq("a", 1).eq("b", 2) |
WHERE (a = :p0 AND b = :p1) |
.eq("a", 1).and(n -> n.eq("b", 2)) |
WHERE (a = :p0 AND b = :p1) |
.eq("a", 1).or(n -> n.eq("b", 2)) |
WHERE (a = :p0 OR b = :p1) |
.eq("a", 1).and(n -> n.eq("b", 2).eq("c", 3)) |
WHERE (a = :p0 AND (b = :p1 AND c = :p2)) |
.eq("a", 1).or(n -> n.eq("b", 2).eq("c", 3)) |
WHERE (a = :p0 OR (b = :p1 AND c = :p2)) |
.eq("a", 1).and(n -> n.eq("b", 2).or(o -> o.eq("c", 3))) |
WHERE (a = :p0 AND (b = :p1 OR c = :p2)) |
.eq("a", 1).or(n -> n.eq("b", 2).or(o -> o.eq("c", 3))) |
WHERE (a = :p0 OR (b = :p1 OR c = :p2)) |
eq(column, null) 会渲染为 column IS NULL,ne(column, null) 会渲染为
column IS NOT NULL;空 IN 渲染为 1 = 0,空 NOT IN 渲染为 1 = 1。
如果实体配置了逻辑删除字段,Repository 查询构建时还会自动追加未删除条件。
conditions() 仅为历史兼容 API,新的执行链以 expression() 生成的条件表达式为准。
需要避免手写字段名时,可以使用 LambdaQueryWrapper、LambdaUpdateWrapper 和
LambdaDeleteWrapper。它们通过实体属性方法引用解析数据库列名。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Mono<Page<User>> result = userRepository.selectPage(
PageRequest.of(0, 20),
new QueryWrapper<User>().eq("status", 1)
);PageRequest 的页码从 0 开始。QueryWrapper 的 page(pageNo, pageSize) 使用从 1 开始的业务页码,并自动设置 limit 和 offset。
默认逻辑删除配置为:
wcdk:
r2dbc:
logic-delete-field: delFlg
logic-not-delete-value: 0
logic-delete-value: 1实体包含对应字段时,查询、统计、存在性判断、更新和派生删除会自动过滤已删除数据。物理删除和逻辑删除的具体方法行为以实体元数据和仓储方法类型为准。
配置多个数据源时,将 spring.r2dbc 改为 spring.r2dbc.data-sources,并指定主数据源:
spring:
r2dbc:
primary: master
data-sources:
master:
url: r2dbc:postgresql://localhost:5432/master
username: postgres
password: postgres
report:
url: r2dbc:postgresql://localhost:5432/report
username: postgres
password: postgres在 Service 或方法上指定数据源:
@R2dbcDataSource("report")
public Flux<UserReport> queryReport() {
return reportRepository.findAll();
}注解包路径为 com.wcdk.r2dbc.datasource.R2dbcDataSource。单数据源场景继续使用 spring.r2dbc.url;只有在由 WCDK 创建多数据源时才需要配置 spring.r2dbc.data-sources 和 primary。
数据源标识通过 Reactor Context 传递;不要使用普通 ThreadLocal 假设数据源上下文一定存在。
Spring 环境下优先使用响应式事务:
@Service
@RequiredArgsConstructor
public class UserService {
private final TransactionalOperator transactionalOperator;
private final UserRepository userRepository;
public Mono<User> create(User user) {
return transactionalOperator.transactional(
userRepository.insert(user)
);
}
}事务中应保持数据库操作链的响应式特性,不要在事务范围内调用阻塞 JDBC、阻塞 HTTP 或长时间外部服务。
WCDK R2DBC 提供响应式 ReactiveTransactionManager,因此 Spring 的
org.springframework.transaction.annotation.Transactional 可以用于 Mono 和
Flux 返回值的方法。事务在 Publisher 被订阅时开启,在 Publisher 正常完成时提交,
发生异常或取消时回滚。
应用中启用 Spring 注解事务管理:
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
}然后在 Spring 管理的 Service Bean 上使用 @Transactional:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public Mono<User> create(User user) {
return userRepository.insert(user)
.flatMap(saved -> userRepository.updateById(saved)
.thenReturn(saved));
}
}WCDK 自动配置会提供 ReactiveTransactionManager。如果应用已经启用了 Spring 标准事务
Advisor,WCDK 自定义事务切面会自动让位,不会重复拦截。
如果应用没有启用 Spring 标准事务 Advisor,可以显式开启 WCDK 切面:
wcdk:
r2dbc:
enabled: true
transaction:
aspect-enabled: trueWCDK 切面会将 @Transactional 方法转换为基于 TransactionalOperator 的响应式事务,
并支持 propagation、isolation、readOnly 和 timeout 等事务属性。
- 方法必须由 Spring Bean 代理调用;直接
new对象或同一个类中使用this.method()自调用, 不会经过事务代理。 - 响应式事务方法应返回
Mono或Flux,不要在方法中调用block()或手动subscribe()。 - 只有返回的响应式链真正被订阅时,事务才会执行;仅创建 Publisher 不会立即开启事务。
- 事务边界内应只放置相关的 R2DBC 数据库操作,不要包含阻塞 JDBC、阻塞 HTTP 或长时间外部调用。
- 事务中的数据库操作必须使用同一个
ConnectionFactory。多数据源场景下,应在事务开始前确定数据源, 事务中不能切换到其他数据源。 - 如果方法返回普通对象而不是
Mono/Flux,不能按响应式事务使用;推荐改为返回Mono<T>或Flux<T>。 - 默认情况下,WCDK 自定义事务切面关闭;既没有 Spring 标准事务 Advisor,也没有配置
wcdk.r2dbc.transaction.aspect-enabled=true时,@Transactional不会生效。
需要明确控制事务边界,或不希望依赖 AOP 代理时,直接使用 TransactionalOperator:
public Mono<User> create(User user) {
return transactionalOperator.transactional(
userRepository.insert(user)
.flatMap(saved -> userRepository.updateById(saved)
.thenReturn(saved))
);
}@Transactional 和 TransactionalOperator 使用同一个响应式事务管理器,不能在同一条业务链上
重复包裹事务,除非确实需要通过 REQUIRES_NEW 等传播行为创建独立事务。
默认扫描路径为 classpath*:repository/**/*.xml。例如 src/main/resources/repository/UserRepository.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE repository PUBLIC "-//WCDK/wcdk-r2dbc" "wcdk-r2dbc-repository.dtd">
<repository namespace="com.example.repository.UserRepository">
<select id="findActiveByEmail" resultType="com.example.User">
SELECT id, user_name, email, status
FROM sys_user
WHERE email = #{email}
AND status = 1
</select>
</repository>仓储接口声明对应方法:
Mono<User> findActiveByEmail(String email);XML 语句支持动态 SQL、参数绑定、resultType、resultMap、discriminator 和 <foreach>。XML 文件中的 namespace、语句 ID、参数和返回类型必须与仓储接口一致。
参数可以使用 #{name} 或 :name 两种写法;两者都会转换为绑定参数,SQL 字符串和注释中的同名文本不会被当成参数。#{...} 还支持条件表达式,可以根据当前方法参数或实体属性选择要写入的值:
<update id="updateProfile">
UPDATE sys_user
SET user_name = #{userName},
update_time = #{updateTime != null ? updateTime : now()},
remark = #{remark != null ? remark : '未填写'},
score = #{score != null ? score : 0.5}
WHERE id = :id
</update>条件和普通属性表达式使用 Spring SpEL 读取当前参数;表达式选中的属性值、字符串和数字会作为绑定参数传给数据库。字符串字面量请用单引号包裹。像 now() 这样的数据库函数会作为 SQL 表达式直接写入 SQL,不会作为字符串参数绑定。实体作为方法参数时可以直接引用其属性,例如 #{updateTime};也可以用 #{updateTime != null ? updateTime : now()} 在属性为空时采用数据库函数。
可以通过 SqlLifecycleInterceptor、ReactiveSqlLifecycleInterceptor 和 SqlExecutionObserver 扩展 SQL 执行生命周期:
@Bean
SqlExecutionObserver sqlExecutionObserver(ObservationRegistry registry) {
return new MicrometerSqlExecutionObserver(registry);
}可观测信息包括 SQL 执行阶段、终止状态、结果数量、耗时和异常。不要把密码、Token、完整身份证号或原始敏感参数写入日志和观测标签。
| 数据库 | R2DBC 驱动 | Maven profile |
|---|---|---|
| 达梦 | com.dameng:dm-r2dbc |
dm |
| PostgreSQL | org.postgresql:r2dbc-postgresql |
postgres |
| MySQL | io.asyncer:r2dbc-mysql |
mysql |
| Oracle | com.oracle.database.r2dbc:oracle-r2dbc |
oracle |
本地构建全部数据库驱动可以使用:
mvn -Pall test- Controller、Service、Repository 链路统一返回
Mono或Flux。 - 业务代码禁止调用
block()、blockFirst()、blockLast()。 - 业务组件禁止手动
subscribe()。 - 阻塞 SDK 必须使用
Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic())隔离,并注明阻塞来源。 - 远程调用应设置超时;重试必须有次数上限、条件和幂等性依据。
- 大量元素使用
flatMap时应设置合理并发上限。 - 异常使用
switchIfEmpty、onErrorMap、onErrorResume和doOnError按语义处理,不能无条件吞异常。
com.wcdk.r2dbc
├── annotation # 公共注解
├── config # 自动配置、仓储扫描、属性配置
├── datasource # 动态数据源与 Reactor Context
├── dialect # 数据库方言
├── repository # 仓储公共接口
├── execution # 仓储执行接口
├── query # QueryWrapper、Lambda Wrapper 与查询表达式
│ ├── sql # SQL 表达式渲染
│ └── xml # XML SQL 与结果映射
└── database # 各数据库驱动适配
mvn test
mvn -DskipTests package响应式单元测试推荐使用 StepVerifier,WebFlux 接口测试推荐使用 WebTestClient。
本项目基于 MIT License 发布。
欢迎提交 Issue 和 Pull Request!
- 作者:WCDK
- 邮箱:wcdk1024@gmail.com