此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
使用 R2DBC 访问数据
R2DBC(“响应式关系数据库连接”)是一种社区驱动的 规范工作,以使用响应式模式标准化对 SQL 数据库的访问。
包层次结构
Spring Framework 的 R2DBC 抽象框架由两个不同的包组成:
-
core
:这org.springframework.r2dbc.core
包包含DatabaseClient
班级加上各种相关班级。请参阅使用 R2DBC 核心类控制基本 R2DBC 处理和错误处理。 -
connection
:这org.springframework.r2dbc.connection
包包含一个实用程序类 为了方便ConnectionFactory
访问和各种简单的ConnectionFactory
实现 可用于测试和运行未修改的 R2DBC。请参阅控制数据库连接。
使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理
本节介绍如何使用 R2DBC 核心类来控制基本的 R2DBC 处理, 包括错误处理。它包括以下主题:
用DatabaseClient
DatabaseClient
是 R2DBC 核心包中的中心类。它处理
创建和释放资源,这有助于避免常见错误,例如
忘记关闭连接。它执行核心 R2DBC 的基本任务
工作流(如语句创建和执行),让应用程序代码提供
SQL 和提取结果。这DatabaseClient
类:
-
运行 SQL 查询
-
更新语句和存储过程调用
-
执行迭代
Result
实例 -
捕获 R2DBC 异常并将它们转换为通用的、信息量更丰富的 异常层次结构
org.springframework.dao
包。 (请参阅一致异常层次结构。
客户端有一个功能齐全的流畅 API,使用响应式类型进行声明性组合。
当您使用DatabaseClient
对于您的代码,您只需实现java.util.function
接口,为它们提供明确定义的契约。
给定一个Connection
由DatabaseClient
类,一个Function
回调会创建一个Publisher
.对于映射函数也是如此
提取一个Row
结果。
您可以使用DatabaseClient
通过直接实例化在 DAO 实现中
使用ConnectionFactory
引用,也可以在 Spring IoC 容器中配置它
并将其作为 bean 参考提供给 DAO。
创建DatabaseClient
object 是通过静态工厂方法,如下所示:
-
Java
-
Kotlin
DatabaseClient client = DatabaseClient.create(connectionFactory);
val client = DatabaseClient.create(connectionFactory)
这ConnectionFactory 应该始终在 Spring IoC 中配置为 bean
容器。 |
前面的方法创建了一个DatabaseClient
使用默认设置。
您还可以获得Builder
实例来自DatabaseClient.builder()
.
您可以通过调用以下方法自定义客户端:
-
….bindMarkers(…)
:提供特定的BindMarkersFactory
配置命名 参数到数据库绑定标记翻译。 -
….executeFunction(…)
:设置ExecuteFunction
如何Statement
对象获取 跑。 -
….namedParameters(false)
:禁用命名参数扩展。默认启用。
方言由BindMarkersFactoryResolver 从ConnectionFactory ,通常通过检查ConnectionFactoryMetadata .你可以让 Spring 自动发现你的 BindMarkersFactory 通过注册
实现org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider 通过META-INF/spring.factories .BindMarkersFactoryResolver 从
使用 Spring 的SpringFactoriesLoader . |
当前支持的数据库包括:
-
H2
-
玛丽亚数据库
-
Microsoft SQL Server
-
MySQL
-
Postgres
此类发出的所有 SQL 都记录在DEBUG
类别下的级别
对应于客户端实例的完全限定类名(通常DefaultDatabaseClient
).此外,每次执行都会在
响应式序列以帮助调试。
以下部分提供了一些示例DatabaseClient
用法。这些例子
不是DatabaseClient
.
请参阅随附的 javadoc 。
执行语句
DatabaseClient
提供运行语句的基本功能。
以下示例显示了需要包含哪些内容才能实现最小但功能齐全
创建新表的代码:
-
Java
-
Kotlin
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
DatabaseClient
专为方便、流畅的使用而设计。
它公开了中间方法、延续方法和终端方法的每个阶段
执行规范。上面的示例使用then()
返回完成Publisher
在查询(或查询,如果 SQL 查询包含
多个语句)完成。
execute(…) 接受 SQL 查询字符串或查询Supplier<String> 将实际查询创建推迟到执行。 |
查询 (SELECT
)
SQL 查询可以通过Row
对象或受影响的行数。DatabaseClient
可以返回更新的行数或行本身,
取决于发出的查询。
以下查询获取id
和name
表中的列:
-
Java
-
Kotlin
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
以下查询使用绑定变量:
-
Java
-
Kotlin
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
您可能已经注意到fetch()
在上面的例子中。fetch()
是一个
continuation 运算符,用于指定要使用的数据量。
叫first()
返回结果中的第一行并丢弃其余行。
您可以使用以下运算符使用数据:
-
first()
返回整个结果的第一行。其 Kotlin 协程变体 被命名为awaitSingle()
对于不可为 null 的返回值,以及awaitSingleOrNull()
如果该值是可选的。 -
one()
只返回一个结果,如果结果包含更多行,则失败。 使用 Kotlin 协程,awaitOne()
对于一个值或awaitOneOrNull()
如果值可能为null
. -
all()
返回结果的所有行。使用 Kotlin 协程时,请使用flow()
. -
rowsUpdated()
返回受影响的行数 (INSERT
/UPDATE
/DELETE
计数)。其 Kotlin 协程变体名为awaitRowsUpdated()
.
如果不指定进一步的映射详细信息,查询将返回表格结果
如Map
其键是映射到其列值的不区分大小写的列名。
您可以通过提供Function<Row, T>
这得到了
每个Row
因此它可以返回任意值(奇异值,
集合和地图以及对象)。
以下示例提取name
列并发出其值:
-
Java
-
Kotlin
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
或者,有一个映射到单个值的快捷方式:
Flux<String> names = client.sql("SELECT name FROM person")
.mapValue(String.class)
.all();
或者,您可以映射到具有 bean 属性或记录组件的结果对象:
// assuming a name property on Person
Flux<Person> persons = client.sql("SELECT name FROM person")
.mapProperties(Person.class)
.all();
更新 (INSERT
,UPDATE
和DELETE
) 替换为DatabaseClient
修改语句的唯一区别是这些语句通常
不要返回表格数据,因此使用rowsUpdated()
消费结果。
以下示例显示了UPDATE
返回数字的语句
更新行数:
-
Java
-
Kotlin
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
将值绑定到查询
典型的应用程序需要参数化的 SQL 语句来选择或
根据某些输入更新行。这些通常是SELECT
语句
受WHERE
子句或INSERT
和UPDATE
接受的语句
输入参数。参数化语句在以下情况下承担 SQL 注入的风险
参数未正确转义。DatabaseClient
利用 R2DBC 的bind
API 以消除查询参数的 SQL 注入风险。
您可以提供参数化 SQL 语句,其中包含execute(…)
算子
并将参数绑定到实际的Statement
.然后,您的 R2DBC 驱动程序运行
使用准备好的语句和参数替换来调用语句。
参数绑定支持两种绑定策略:
-
按索引,使用从零开始的参数索引。
-
按名称,使用占位符名称。
以下示例显示了查询的参数绑定:
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
或者,您可以传入名称和值的映射:
Map<String, Object> params = new LinkedHashMap<>();
params.put("id", "joe");
params.put("name", "Joe");
params.put("age", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(params);
或者,您可以传入具有 bean 属性或记录组件的参数对象:
// assuming id, name, age properties on Person
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindProperties(new Person("joe", "Joe", 34);
或者,您可以使用位置参数将值绑定到语句。 指数从零开始。
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
.bind(2, 34);
如果您的应用程序绑定到许多参数,则可以通过单个调用实现相同的效果:
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
名为Collection
参数转换为一系列绑定
标记,以消除基于参数数量的动态查询创建的需要。
嵌套对象数组被扩展以允许使用(例如)选择列表。
请考虑以下查询:
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
可以对上述查询进行参数化并按如下方式运行:
-
Java
-
Kotlin
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
选择列表的使用取决于提供商。 |
以下示例显示了一个更简单的变体,使用IN
谓词:
-
Java
-
Kotlin
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", arrayOf(35, 50))
R2DBC 本身不支持类似集合的值。不过
扩展给定的List 在上面的示例中,适用于命名参数
例如,在 Spring 的 R2DBC 支持中,用于IN 如上所示的条款。
但是,插入或更新数组类型的列(例如,在 Postgres 中)
需要底层 R2DBC 驱动程序支持的数组类型:
通常是 Java 数组,例如String[] 要更新text[] 列。
不通过Collection<String> 或类似作为数组参数。 |
语句过滤器
有时您需要在实际的Statement
在它运行之前。为此,请注册一个Statement
Filter
(StatementFilterFunction
) 替换为DatabaseClient
拦截和
modify 语句,如以下示例所示:
-
Java
-
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
DatabaseClient
还公开了简化的filter(…)
接受
一个Function<Statement, Statement>
:
-
Java
-
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
StatementFilterFunction
实现允许过滤Statement
和过滤Result
对象。
DatabaseClient
最佳实践
的实例DatabaseClient
一旦配置,类是线程安全的。这是
重要,因为这意味着您可以配置DatabaseClient
然后安全地将此共享引用注入多个 DAO(或存储库)。
这DatabaseClient
是有状态的,因为它维护对ConnectionFactory
,
但这种状态不是对话状态。
使用DatabaseClient
class 是配置一个ConnectionFactory
在 Spring 配置文件中,然后 dependency-inject
那共享ConnectionFactory
bean 加入你的 DAO 类。这DatabaseClient
创建于
的 setterConnectionFactory
.这导致了类似于以下内容的 DAO:
-
Java
-
Kotlin
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory);
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
private val databaseClient = DatabaseClient.create(connectionFactory)
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
显式配置的替代方法是使用组件扫描和注释
支持依赖注入。在这种情况下,您可以使用@Component
(这使其成为组件扫描的候选者)并注释ConnectionFactory
塞特
方法@Autowired
.以下示例显示了如何执行此作:
-
Java
-
Kotlin
@Component (1)
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
@Autowired (2)
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory); (3)
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Component . |
2 | 注释ConnectionFactory setter 方法与@Autowired . |
3 | 创建一个新的DatabaseClient 使用ConnectionFactory . |
@Component (1)
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { (2)
private val databaseClient = DatabaseClient(connectionFactory) (3)
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1 | 用@Component . |
2 | 构造函数注入ConnectionFactory . |
3 | 创建一个新的DatabaseClient 使用ConnectionFactory . |
无论您选择使用上述哪种模板初始化样式(或
not),很少需要创建DatabaseClient
每个类别
时间。配置后,一个DatabaseClient
实例是线程安全的。
如果您的应用程序访问多个
databases,您可能需要多个DatabaseClient
实例,这需要多个ConnectionFactory
随后,多个不同配置的DatabaseClient
实例。
检索自动生成的键
INSERT
语句在将行插入表时可能会生成键
定义自动递增或标识列。要完全控制
要生成的列名,只需注册一个StatementFilterFunction
那
请求为所需列生成的键。
-
Java
-
Kotlin
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"))
.map(row -> row.get("id", Integer.class))
.first();
// generatedId emits the generated key once the INSERT statement has finished
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.map { row -> row.get("id", Integer.class) }
.awaitOne()
// generatedId emits the generated key once the INSERT statement has finished
控制数据库连接
本节涵盖:
用ConnectionFactory
Spring 通过ConnectionFactory
.
一个ConnectionFactory
是 R2DBC 规范的一部分,是一个常见的入口点
对于Drivers。它允许容器或框架隐藏连接池
以及应用程序代码中的事务管理问题。作为开发者,
您无需了解有关如何连接到数据库的详细信息。那就是
设置ConnectionFactory
.你
在开发和测试代码时,很可能会同时担任这两个角色,但您没有
必须知道生产数据源是如何配置的。
当您使用 Spring 的 R2DBC 层时,您可以使用第三方提供的连接池实现来配置您自己的层。一个流行的实现是 R2DBC 池(r2dbc-pool
). Spring 中的实现分发仅用于测试目的,不提供池化。
要配置ConnectionFactory
:
-
获取与
ConnectionFactory
因为您通常会获得 R2DBCConnectionFactory
. -
提供 R2DBC URL(有关正确的值,请参阅驱动程序的文档)。
以下示例演示如何配置ConnectionFactory
:
-
Java
-
Kotlin
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
用ConnectionFactoryUtils
这ConnectionFactoryUtils
class 是一个方便且功能强大的辅助类它提供了static
从ConnectionFactory
并关闭连接(如有必要)。
它支持订阅者Context
-绑定连接,例如,与R2dbcTransactionManager
.
用SingleConnectionFactory
这SingleConnectionFactory
class 是DelegatingConnectionFactory
将单个Connection
每次使用后不会关闭。
如果任何客户端代码调用close
假设池连接(如使用
持久化工具),您应该将suppressClose
属性设置为true
.此设置
返回包装物理连接的紧密抑制代理。请注意,您可以
不再将此内容强制转换为原生Connection
或类似对象。
SingleConnectionFactory
主要是一个测试类,可用于特定要求
例如流水线(如果您的 R2DBC 驱动程序允许此类使用)。
与池ConnectionFactory
,它始终重复使用相同的连接,避免了
过度创建物理连接。
用TransactionAwareConnectionFactoryProxy
TransactionAwareConnectionFactoryProxy
是目标的代理ConnectionFactory
.
代理包装该目标ConnectionFactory
以增加对 Spring 管理的事务的认识。
如果您使用未集成的 R2DBC 客户端,则需要使用此类
与 Spring 的 R2DBC 支持。在这种情况下,您仍然可以使用此客户端,并且
同时,让这个客户端参与 Spring 管理的事务。一般是
最好集成具有适当访问权限的 R2DBC 客户端ConnectionFactoryUtils 用于资源管理。 |
请参阅TransactionAwareConnectionFactoryProxy
javadoc 了解更多详情。
用R2dbcTransactionManager
这R2dbcTransactionManager
class 是一个ReactiveTransactionManager
实现
单个 R2DBCConnectionFactory
.它绑定一个 R2DBCConnection
从指定的ConnectionFactory
给订阅者Context
,可能允许一个订阅者Connection
对于每个ConnectionFactory
.
检索 R2DBC 需要应用程序代码Connection
通过ConnectionFactoryUtils.getConnection(ConnectionFactory)
,而不是 R2DBC 的标准ConnectionFactory.create()
.所有框架类(例如DatabaseClient
)使用这个
隐含的策略。如果不与事务管理器一起使用,则查找策略的行为
一模一样ConnectionFactory.create()
因此可以在任何情况下使用。