R2DBC(“反应式关系数据库连接”)是一个社区驱动的 规范工作,以使用反应模式标准化对 SQL 数据库的访问。
包层次结构
Spring 框架的 R2DBC 抽象框架由两个不同的包组成:
- 
core:该包包含类以及各种相关类。请参阅使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理。org.springframework.r2dbc.coreDatabaseClient - 
connection:包包含一个实用程序类 用于轻松访问和各种简单的实施 可用于测试和运行未修改的 R2DBC。请参阅控制数据库连接。org.springframework.r2dbc.connectionConnectionFactoryConnectionFactory 
使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理
本节介绍如何使用 R2DBC 核心类来控制基本的 R2DBC 处理。 包括错误处理。它包括以下主题:
用DatabaseClient
DatabaseClient是 R2DBC 核心包中的中心类。它处理
创建和释放资源,这有助于避免常见错误,例如
忘记关闭连接。它执行内核 R2DBC 的基本任务
工作流(例如语句创建和执行),让应用程序代码提供
SQL 并提取结果。类:DatabaseClient
- 
运行 SQL 查询
 - 
更新语句和存储过程调用
 - 
对实例执行迭代
Result - 
捕获 R2DBC 异常并将它们转换为通用的、信息量更大的 Exception 层次结构。 (请参阅一致的异常层次结构。
org.springframework.dao 
客户端有一个功能齐全的 Fluent API,使用反应式类型进行声明式组合。
当你将 the 用于你的代码时,你只需要实现接口,为它们提供一个明确定义的协定。
给定类提供的 a,回调会创建一个 .对于映射函数也是如此,这些函数
提取结果。DatabaseClientjava.util.functionConnectionDatabaseClientFunctionPublisherRow
您可以通过直接实例化在 DAO 实现中使用
替换为引用,或者您可以在 Spring IoC 容器中配置它
并将其作为 bean 引用提供给 DAO。DatabaseClientConnectionFactory
创建对象的最简单方法是通过静态工厂方法,如下所示:DatabaseClient
- 
Java
 - 
Kotlin
 
DatabaseClient client = DatabaseClient.create(connectionFactory);
val client = DatabaseClient.create(connectionFactory)
应该始终在 Spring IoC 中配置为 bean
容器。ConnectionFactory | 
上述方法使用默认设置创建一个。DatabaseClient
您还可以从 获取实例。
您可以通过以下方法自定义客户端:BuilderDatabaseClient.builder()
- 
….bindMarkers(…):提供特定的 to configure named 参数绑定到数据库绑定标记翻译。BindMarkersFactory - 
….executeFunction(…):设置对象获取 跑。ExecuteFunctionStatement - 
….namedParameters(false):禁用命名参数扩展。默认启用。 
方言由 BindMarkersFactoryResolver 从 中解析,通常是通过检查 .你可以让 Spring 通过注册一个 通过 . 发现 Bind Marker Provider 实现 使用 Spring 的 . ConnectionFactoryConnectionFactoryMetadataBindMarkersFactoryorg.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProviderMETA-INF/spring.factoriesBindMarkersFactoryResolverSpringFactoriesLoader | 
当前支持的数据库包括:
- 
H2 系列
 - 
MariaDB的
 - 
Microsoft SQL 服务器
 - 
MySQL (MySQL的
 - 
Postgres
 
此类发出的所有 SQL 都记录在类别
对应于 Client 端实例的完全限定类名(通常为 )。此外,每次执行都会在
reactive 序列来帮助调试。DEBUGDefaultDatabaseClient
以下部分提供了一些用法示例。这些例子
并不是 .
有关此内容,请参阅随附的 javadoc。DatabaseClientDatabaseClient
执行语句
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旨在方便、流畅地使用。
它在
执行规范。上面的示例用于返回一个完成,该完成在查询(或 queries,如果 SQL 查询包含
multiple statements) 完成。then()Publisher
execute(…)接受 SQL 查询字符串或查询,以将实际的查询创建推迟到执行。Supplier<String> | 
查询 (SELECT)
SQL 查询可以通过对象或受影响的行数返回值。 可以返回更新的行数或行本身,
取决于发出的查询。RowDatabaseClient
以下查询从表中获取 and 列:idname
- 
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()
以下查询使用 bind 变量:
- 
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()
您可能已经注意到了上面示例中的 of 用法。 是一个
continuation 运算符,用于指定要使用的数据量。fetch()fetch()
调用 返回结果中的第一行并丢弃其余行。
您可以使用以下运算符来使用数据:first()
- 
first()返回整个结果的第一行。其 Kotlin 协程变体 针对不可为 null 的返回值命名,并且该值是可选的。awaitSingle()awaitSingleOrNull() - 
one()只返回一个结果,如果结果包含更多行,则失败。 使用 Kotlin 协程,对于一个值,或者该值可能是 .awaitOne()awaitOneOrNull()null - 
all()返回结果的所有行。使用 Kotlin 协程时,请使用 .flow() - 
rowsUpdated()返回受影响的行数 (// count)。其 Kotlin 协程变体名为 。INSERTUPDATEDELETEawaitRowsUpdated() 
在不指定进一步的映射详细信息的情况下,查询将返回表格结果
as 的键是映射到其列值的不区分大小写的列名。Map
您可以通过提供一个 get
调用 for each 以便它可以返回任意值(奇异值、
集合和映射以及对象)。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();
更新 (, , 和 )INSERTUPDATEDELETEDatabaseClient
修改语句的唯一区别是这些语句通常
不返回表格数据,以便您用于使用结果。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 或
根据一些输入更新 Rows。这些通常是语句
受子句约束的 OR AND 语句接受
input 参数。如果满足以下条件,参数化语句会有 SQL 注入的风险
参数没有正确转义。 利用 R2DBC 的 API 来消除查询参数的 SQL 注入风险。
您可以使用运算符
并将参数绑定到实际的 .然后,您的 R2DBC 驱动程序运行
使用准备好的语句和参数替换的语句。SELECTWHEREINSERTUPDATEDatabaseClientbindexecute(…)Statement
参数绑定支持两种绑定策略:
- 
By Index,使用从 0 开始的参数索引。
 - 
By Name (按名称) 使用占位符名称。
 
以下示例显示了查询的参数绑定:
    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);
query-preprocessor 将命名参数展开到一系列 bind 中
标记,无需根据参数数量创建动态查询。
嵌套对象数组已扩展以允许使用(例如)选择列表。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 本身不支持类似 Collection 的值。不过
在上面的例子中展开 given 适用于命名参数
在 Spring 的 R2DBC 支持中,例如用于上面所示的子句中。
但是,插入或更新数组类型的列(例如在 Postgres 中)
需要底层 R2DBC 驱动程序支持的数组类型:
通常是一个 Java 数组,例如 以更新列。
不要将 or 等作为数组参数传递。ListINString[]text[]Collection<String> | 
语句筛选器
有时,您需要在实际运行之前微调选项。为此,请注册一个过滤器
() 替换为 to intercept 和
modify 语句的执行情况,如下例所示:StatementStatementStatementFilterFunctionDatabaseClient
- 
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) }
StatementFilterFunctionimplementations 允许过滤 the 和 filter of objects。StatementResult
DatabaseClient最佳实践
类的实例一旦配置,就是线程安全的。这是
重要,因为这意味着您可以配置单个实例,然后将此共享引用安全地注入多个 DAO(或存储库)。
它是有状态的,因为它维护对 ,
但此状态不是对话状态。DatabaseClientDatabaseClientDatabaseClientConnectionFactory
使用该类时的常见做法是在 Spring 配置文件中配置 a,然后 dependency-inject
将 bean 共享到您的 DAO 类中。在
.这导致了类似于以下内容的 DAO:DatabaseClientConnectionFactoryConnectionFactoryDatabaseClientConnectionFactory
- 
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...
}
显式配置的替代方法是使用组件扫描和注释
支持依赖项注入。在这种情况下,你可以用 (这使它成为组件扫描的候选者) 来注释类,并 注释 setter
方法与 .以下示例显示了如何执行此操作:@ComponentConnectionFactory@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@Autowired | 
| 3 | 使用 创建新的 .DatabaseClientConnectionFactory | 
@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 | 使用 创建新的 .DatabaseClientConnectionFactory | 
无论您选择使用上述哪种模板初始化样式(或
not),则很少需要为每个
运行时间。配置后,实例是线程安全的。
如果您的应用程序访问多个
databases 中,您可能需要多个实例,这需要多个实例,然后需要多个不同配置的实例。DatabaseClientDatabaseClientDatabaseClientConnectionFactoryDatabaseClient
应该始终在 Spring IoC 中配置为 bean
容器。ConnectionFactory | 
方言由 BindMarkersFactoryResolver 从 中解析,通常是通过检查 .你可以让 Spring 通过注册一个 通过 . 发现 Bind Marker Provider 实现 使用 Spring 的 . ConnectionFactoryConnectionFactoryMetadataBindMarkersFactoryorg.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProviderMETA-INF/spring.factoriesBindMarkersFactoryResolverSpringFactoriesLoader | 
execute(…)接受 SQL 查询字符串或查询,以将实际的查询创建推迟到执行。Supplier<String> | 
| 选择列表的使用取决于供应商。 | 
R2DBC 本身不支持类似 Collection 的值。不过
在上面的例子中展开 given 适用于命名参数
在 Spring 的 R2DBC 支持中,例如用于上面所示的子句中。
但是,插入或更新数组类型的列(例如在 Postgres 中)
需要底层 R2DBC 驱动程序支持的数组类型:
通常是一个 Java 数组,例如 以更新列。
不要将 or 等作为数组参数传递。ListINString[]text[]Collection<String> | 
| 1 | 用 .@Component | 
| 2 | 使用 .ConnectionFactory@Autowired | 
| 3 | 使用 创建新的 .DatabaseClientConnectionFactory | 
| 1 | 用 .@Component | 
| 2 | 构造函数注入 .ConnectionFactory | 
| 3 | 使用 创建新的 .DatabaseClientConnectionFactory | 
检索自动生成的密钥
INSERT语句可能会在表中插入行时生成键
定义 auto-increment 或 identity 列。要完全控制
要生成的列名,只需注册一个
请求为所需列生成的键。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 通过 .
A 是 R2DBC 规范的一部分,是一个常见的入口点
对于Drivers。它允许容器或框架隐藏连接池
以及应用程序代码中的事务管理问题。作为开发人员,
您无需了解有关如何连接到数据库的详细信息。那就是
设置 的管理员的责任。你
在开发和测试代码时,很可能会同时担任这两个角色,但您不会
必须知道生产数据源的配置方式。ConnectionFactoryConnectionFactoryConnectionFactory
当您使用 Spring 的 R2DBC 层时,您可以使用
连接池实现由第三方提供。一个流行的
implementation 是 R2DBC Pool ()。Spring 中的实现
distribution 仅用于测试目的,不提供池化。r2dbc-pool
要配置 :ConnectionFactory
- 
获取连接,就像通常获取 R2DBC 一样。
ConnectionFactoryConnectionFactory - 
提供 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
该类是一个方便而强大的辅助类
,它提供了从连接获取和关闭连接的方法(如有必要)。ConnectionFactoryUtilsstaticConnectionFactory
它支持订阅者绑定连接,例如 .ContextR2dbcTransactionManager
用SingleConnectionFactory
该类是 interface 的实现,它包装了一个在每次使用后都不会关闭的 single。SingleConnectionFactoryDelegatingConnectionFactoryConnection
如果任何客户端代码在假设池连接的情况下调用(如使用
持久性工具),则应将该属性设置为 。此设置
返回包装物理连接的关闭抑制代理。请注意,您可以
不再将此对象强制转换为本机或类似对象。closesuppressClosetrueConnection
SingleConnectionFactory主要是一个测试类,可用于特定要求
例如 pipelining (如果您的 R2DBC 驱动程序允许此类使用)。
与 pooled 相比,它始终重用相同的连接,从而避免
过度创建物理连接。ConnectionFactory
用TransactionAwareConnectionFactoryProxy
TransactionAwareConnectionFactoryProxy是目标 的代理。
代理包装该目标以增加对 Spring 管理的事务的感知。ConnectionFactoryConnectionFactory
如果您使用未以其他方式集成的 R2DBC 客户端,则需要使用此类
具有 Spring 的 R2DBC 支持。在这种情况下,您仍然可以使用此客户端,并且
同时,让这个 Client 端参与 Spring 管理的事务。它通常是
最好将 R2DBC 客户端与适当的访问权限集成以进行资源管理。ConnectionFactoryUtils | 
有关更多详细信息,请参见 TransactionAwareConnectionFactoryProxy javadoc。
用R2dbcTransactionManager
该类是
单个 R2DBC 。它将 R2DBC 从指定绑定到 subscriber ,可能允许每个 有一个 subscriber 。R2dbcTransactionManagerReactiveTransactionManagerConnectionFactoryConnectionConnectionFactoryContextConnectionConnectionFactory
需要应用程序代码才能通过 检索 R2DBC,而不是 R2DBC 的标准 .所有框架类(例如 )都使用 this
策略。如果不与事务管理器一起使用,则查找策略的行为为
完全一样,因此可以在任何情况下使用。ConnectionConnectionFactoryUtils.getConnection(ConnectionFactory)ConnectionFactory.create()DatabaseClientConnectionFactory.create()
如果您使用未以其他方式集成的 R2DBC 客户端,则需要使用此类
具有 Spring 的 R2DBC 支持。在这种情况下,您仍然可以使用此客户端,并且
同时,让这个 Client 端参与 Spring 管理的事务。它通常是
最好将 R2DBC 客户端与适当的访问权限集成以进行资源管理。ConnectionFactoryUtils |