R2DBC SPI 速查
创建连接
ConnectionFactory factory = new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost").port(5432).database("eventd")
.username("eventd").password("eventd")
.build()
);
Mono<Connection> conn = Mono.from(factory.create());
拿到 Connection 只是开始,必须保证归还。推荐直接用 usingWhen 把「获取 → 使用 → 关闭」包成一条链:
Mono<User> user = Mono.usingWhen(
Mono.from(factory.create()),
c -> Mono.from(c.createStatement("SELECT * FROM users WHERE id = $1")
.bind("$1", 1)
.execute())
.flatMap(result -> Mono.from(result.map((row, meta) ->
new User(row.get("id", Long.class), row.get("name", String.class))))),
c -> Mono.from(c.close()) // close() 返回 Publisher,必须订阅
);
查询
Mono.from(conn.createStatement("SELECT * FROM users WHERE id = $1")
.bind("$1", 1)
.execute())
.flatMapMany(result -> result.map((row, meta) ->
new User(row.get("id", Long.class), row.get("name", String.class))
));
插入 + 返回生成键
Mono.from(conn.createStatement("INSERT INTO users (name) VALUES ($1)")
.bind("$1", "alice")
.returnGeneratedValues("id")
.execute())
.flatMapMany(result -> result.map((row, meta) -> row.get("id", Long.class)));
批量
Statement stmt = conn.createStatement("INSERT INTO logs (level, msg) VALUES ($1, $2)");
stmt.bind("$1", "INFO").bind("$2", "ok").add();
stmt.bind("$1", "WARN").bind("$2", "low").add();
// 批量时每个 add() 产生一个 Result:用 Flux.from,Mono.from 只取第一个
Flux.from(stmt.execute()).flatMap(Result::getRowsUpdated);
关闭连接
Mono.from(conn.close()); // 惰性:返回 Publisher,尚未执行
Mono.from(conn.close()).subscribe(); // 真正关闭
Mono.from(conn.close()).block(); // 在测试/启动脚本里同步等待关闭
Connection.close() 返回 Publisher<Void>,直接写 conn.close(); 什么都不会发生,连接也不会归还连接池。
DatabaseClient 速查
// 单行查询
db.sql("SELECT * FROM users WHERE id = :id")
.bind("id", 1)
.map((row, meta) -> new User(...))
.one();
// 多行查询
db.sql("SELECT * FROM users WHERE status = :status")
.bind("status", "ACTIVE")
.map((row, meta) -> new User(...))
.all();
// 更新
db.sql("UPDATE users SET status = :status WHERE id = :id")
.bind("status", "INACTIVE")
.bind("id", 1)
.fetch()
.rowsUpdated();
// 存在性检查
db.sql("SELECT 1 FROM users WHERE id = :id")
.bind("id", 1)
.fetch()
.first()
.hasElement();
Repository 派生查询规则
| 关键字 | 示例 | 生成的 WHERE |
|---|---|---|
And | findByNameAndStatus | name = ? AND status = ? |
Or | findByNameOrEmail | name = ? OR email = ? |
Is, Equals | findByNameIs | name = ? |
Like | findByNameLike | name LIKE ?(通配符由调用方提供) |
StartingWith | findByNameStartingWith | name LIKE ?,参数值自动变成 prefix% |
EndingWith | findByNameEndingWith | name LIKE ?,参数值自动变成 %suffix |
Containing | findByNameContaining | name LIKE ?,参数值自动变成 %value% |
GreaterThan | findByAgeGreaterThan | age > ? |
Between | findByAgeBetween | age BETWEEN ? AND ? |
In | findByStatusIn | status IN (?, ?, ...) |
NotIn | findByStatusNotIn | status NOT IN (?, ?, ...) |
OrderBy | findByStatusOrderByNameDesc | ORDER BY name DESC |
True | findByActiveTrue | active = true |
False | findByActiveFalse | active = false |
IgnoreCase | findByNameIgnoreCase | UPPER(name) = UPPER(?) |
Like 和 Containing/StartingWith/EndingWith 的区别:前者把值原样交给 SQL,% 要自己拼;后者由 Spring Data 自动加通配符。混用会出现 %% 或通配符丢失。
小结
本附录汇总 R2DBC SPI 和 Spring Data R2DBC 的常用操作。原始 SPI 用于调试和性能优化,DatabaseClient 用于复杂查询,Repository 用于 CRUD 和简单条件查询。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。