此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

使用 R2DBC 访问数据

R2DBC(“响应式关系数据库连接”)是一种社区驱动的 规范工作,以使用响应式模式标准化对 SQL 数据库的访问。spring-doc.cadn.net.cn

包层次结构

Spring Framework 的 R2DBC 抽象框架由两个不同的包组成:spring-doc.cadn.net.cn

使用 R2DBC 核心类控制基本的 R2DBC 处理和错误处理

本节介绍如何使用 R2DBC 核心类来控制基本的 R2DBC 处理, 包括错误处理。它包括以下主题:spring-doc.cadn.net.cn

DatabaseClient

DatabaseClient是 R2DBC 核心包中的中心类。它处理 创建和释放资源,这有助于避免常见错误,例如 忘记关闭连接。它执行核心 R2DBC 的基本任务 工作流(如语句创建和执行),让应用程序代码提供 SQL 和提取结果。这DatabaseClient类:spring-doc.cadn.net.cn

客户端有一个功能齐全的流畅 API,使用响应式类型进行声明性组合。spring-doc.cadn.net.cn

当您使用DatabaseClient对于您的代码,您只需实现java.util.function接口,为它们提供明确定义的契约。 给定一个ConnectionDatabaseClient类,一个Function回调会创建一个Publisher.对于映射函数也是如此 提取一个Row结果。spring-doc.cadn.net.cn

您可以使用DatabaseClient通过直接实例化在 DAO 实现中 使用ConnectionFactory引用,也可以在 Spring IoC 容器中配置它 并将其作为 bean 参考提供给 DAO。spring-doc.cadn.net.cn

创建DatabaseClientobject 是通过静态工厂方法,如下所示:spring-doc.cadn.net.cn

DatabaseClient client = DatabaseClient.create(connectionFactory);
val client = DatabaseClient.create(connectionFactory)
ConnectionFactory应该始终在 Spring IoC 中配置为 bean 容器。

前面的方法创建了一个DatabaseClient使用默认设置。spring-doc.cadn.net.cn

您还可以获得Builder实例来自DatabaseClient.builder(). 您可以通过调用以下方法自定义客户端:spring-doc.cadn.net.cn

  • ….bindMarkers(…):提供特定的BindMarkersFactory配置命名 参数到数据库绑定标记翻译。spring-doc.cadn.net.cn

  • ….executeFunction(…):设置ExecuteFunction如何Statement对象获取 跑。spring-doc.cadn.net.cn

  • ….namedParameters(false):禁用命名参数扩展。默认启用。spring-doc.cadn.net.cn

方言由BindMarkersFactoryResolverConnectionFactory,通常通过检查ConnectionFactoryMetadata.
你可以让 Spring 自动发现你的
BindMarkersFactory通过注册 实现org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider通过META-INF/spring.factories.BindMarkersFactoryResolver从 使用 Spring 的SpringFactoriesLoader.

当前支持的数据库包括:spring-doc.cadn.net.cn

此类发出的所有 SQL 都记录在DEBUG类别下的级别 对应于客户端实例的完全限定类名(通常DefaultDatabaseClient).此外,每次执行都会在 响应式序列以帮助调试。spring-doc.cadn.net.cn

以下部分提供了一些示例DatabaseClient用法。这些例子 不是DatabaseClient. 请参阅随附的 javadocspring-doc.cadn.net.cn

执行语句

DatabaseClient提供运行语句的基本功能。 以下示例显示了需要包含哪些内容才能实现最小但功能齐全 创建新表的代码:spring-doc.cadn.net.cn

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 查询包含 多个语句)完成。spring-doc.cadn.net.cn

execute(…)接受 SQL 查询字符串或查询Supplier<String>将实际查询创建推迟到执行。

查询 (SELECT)

SQL 查询可以通过Row对象或受影响的行数。DatabaseClient可以返回更新的行数或行本身, 取决于发出的查询。spring-doc.cadn.net.cn

以下查询获取idname表中的列:spring-doc.cadn.net.cn

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()

以下查询使用绑定变量:spring-doc.cadn.net.cn

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 运算符,用于指定要使用的数据量。spring-doc.cadn.net.cn

first()返回结果中的第一行并丢弃其余行。 您可以使用以下运算符使用数据:spring-doc.cadn.net.cn

  • first()返回整个结果的第一行。其 Kotlin 协程变体 被命名为awaitSingle()对于不可为 null 的返回值,以及awaitSingleOrNull()如果该值是可选的。spring-doc.cadn.net.cn

  • one()只返回一个结果,如果结果包含更多行,则失败。 使用 Kotlin 协程,awaitOne()对于一个值或awaitOneOrNull()如果值可能为null.spring-doc.cadn.net.cn

  • all()返回结果的所有行。使用 Kotlin 协程时,请使用flow().spring-doc.cadn.net.cn

  • rowsUpdated()返回受影响的行数 (INSERT/UPDATE/DELETE计数)。其 Kotlin 协程变体名为awaitRowsUpdated().spring-doc.cadn.net.cn

如果不指定进一步的映射详细信息,查询将返回表格结果 如Map其键是映射到其列值的不区分大小写的列名。spring-doc.cadn.net.cn

您可以通过提供Function<Row, T>这得到了 每个Row因此它可以返回任意值(奇异值, 集合和地图以及对象)。spring-doc.cadn.net.cn

以下示例提取name列并发出其值:spring-doc.cadn.net.cn

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()

或者,有一个映射到单个值的快捷方式:spring-doc.cadn.net.cn

	Flux<String> names = client.sql("SELECT name FROM person")
			.mapValue(String.class)
			.all();

或者,您可以映射到具有 bean 属性或记录组件的结果对象:spring-doc.cadn.net.cn

	// assuming a name property on Person
	Flux<Person> persons = client.sql("SELECT name FROM person")
			.mapProperties(Person.class)
			.all();
怎么样null?

关系数据库结果可以包含null值。 Reactive Streams 规范禁止发射null值。 该要求要求适当的null处理。 虽然你可以获得null来自Row,则不得发出null价值。您必须将任何null对象中的值(例如Optional对于单数值)以确保null值永远不会直接返回 通过您的提取器功能。spring-doc.cadn.net.cn

更新 (INSERT,UPDATEDELETE) 替换为DatabaseClient

修改语句的唯一区别是这些语句通常 不要返回表格数据,因此使用rowsUpdated()消费结果。spring-doc.cadn.net.cn

以下示例显示了UPDATE返回数字的语句 更新行数:spring-doc.cadn.net.cn

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子句或INSERTUPDATE接受的语句 输入参数。参数化语句在以下情况下承担 SQL 注入的风险 参数未正确转义。DatabaseClient利用 R2DBC 的bindAPI 以消除查询参数的 SQL 注入风险。 您可以提供参数化 SQL 语句,其中包含execute(…)算子 并将参数绑定到实际的Statement.然后,您的 R2DBC 驱动程序运行 使用准备好的语句和参数替换来调用语句。spring-doc.cadn.net.cn

参数绑定支持两种绑定策略:spring-doc.cadn.net.cn

以下示例显示了查询的参数绑定:spring-doc.cadn.net.cn

	db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
			.bind("id", "joe")
			.bind("name", "Joe")
			.bind("age", 34);

或者,您可以传入名称和值的映射:spring-doc.cadn.net.cn

	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 属性或记录组件的参数对象:spring-doc.cadn.net.cn

	// 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);

或者,您可以使用位置参数将值绑定到语句。 指数从零开始。spring-doc.cadn.net.cn

	db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
			.bind(0, "joe")
			.bind(1, "Joe")
			.bind(2, 34);

如果您的应用程序绑定到许多参数,则可以通过单个调用实现相同的效果:spring-doc.cadn.net.cn

	List<?> values = List.of("joe", "Joe", 34);
	db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
			.bindValues(values);
R2DBC 原生绑定标记

R2DBC 使用依赖于实际数据库提供商的数据库原生绑定标记。 例如,Postgres 使用索引标记,例如$1,$2,$n. 另一个示例是 SQL Server,它使用以 为前缀的命名绑定标记。@spring-doc.cadn.net.cn

这与 JDBC 不同,JDBC 需要?作为绑定标记。 在 JDBC 中,实际的驱动程序?将标记绑定到数据库原生 标记作为其语句执行的一部分。spring-doc.cadn.net.cn

Spring Framework 的 R2DBC 支持允许您使用本机绑定标记或命名绑定 标记与:name语法。spring-doc.cadn.net.cn

命名参数支持利用BindMarkersFactory实例展开名为 参数添加到查询执行时的本机绑定标记,这为您提供了 跨各种数据库提供商的一定程度的查询可移植性。spring-doc.cadn.net.cn

名为Collection参数转换为一系列绑定 标记,以消除基于参数数量的动态查询创建的需要。 嵌套对象数组被扩展以允许使用(例如)选择列表。spring-doc.cadn.net.cn

请考虑以下查询:spring-doc.cadn.net.cn

SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))

可以对上述查询进行参数化并按如下方式运行:spring-doc.cadn.net.cn

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谓词:spring-doc.cadn.net.cn

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在它运行之前。为此,请注册一个StatementFilter (StatementFilterFunction) 替换为DatabaseClient拦截和 modify 语句,如以下示例所示:spring-doc.cadn.net.cn

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>:spring-doc.cadn.net.cn

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对象。spring-doc.cadn.net.cn

DatabaseClient最佳实践

的实例DatabaseClient一旦配置,类是线程安全的。这是 重要,因为这意味着您可以配置DatabaseClient然后安全地将此共享引用注入多个 DAO(或存储库)。 这DatabaseClient是有状态的,因为它维护对ConnectionFactory, 但这种状态不是对话状态。spring-doc.cadn.net.cn

使用DatabaseClientclass 是配置一个ConnectionFactory在 Spring 配置文件中,然后 dependency-inject 那共享ConnectionFactorybean 加入你的 DAO 类。这DatabaseClient创建于 的 setterConnectionFactory.这导致了类似于以下内容的 DAO:spring-doc.cadn.net.cn

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.以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@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 注释ConnectionFactorysetter 方法与@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实例。spring-doc.cadn.net.cn

检索自动生成的键

INSERT语句在将行插入表时可能会生成键 定义自动递增或标识列。要完全控制 要生成的列名,只需注册一个StatementFilterFunction那 请求为所需列生成的键。spring-doc.cadn.net.cn

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

控制数据库连接

本节涵盖:spring-doc.cadn.net.cn

ConnectionFactory

Spring 通过ConnectionFactory. 一个ConnectionFactory是 R2DBC 规范的一部分,是一个常见的入口点 对于Drivers。它允许容器或框架隐藏连接池 以及应用程序代码中的事务管理问题。作为开发者, 您无需了解有关如何连接到数据库的详细信息。那就是 设置ConnectionFactory.你 在开发和测试代码时,很可能会同时担任这两个角色,但您没有 必须知道生产数据源是如何配置的。spring-doc.cadn.net.cn

当您使用 Spring 的 R2DBC 层时,您可以使用第三方提供的连接池实现来配置您自己的层。一个流行的实现是 R2DBC 池(r2dbc-pool). Spring 中的实现分发仅用于测试目的,不提供池化。spring-doc.cadn.net.cn

要配置ConnectionFactory:spring-doc.cadn.net.cn

  1. 获取与ConnectionFactory因为您通常会获得 R2DBCConnectionFactory.spring-doc.cadn.net.cn

  2. 提供 R2DBC URL(有关正确的值,请参阅驱动程序的文档)。spring-doc.cadn.net.cn

以下示例演示如何配置ConnectionFactory:spring-doc.cadn.net.cn

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

ConnectionFactoryUtilsclass 是一个方便且功能强大的辅助类它提供了staticConnectionFactory并关闭连接(如有必要)。spring-doc.cadn.net.cn

它支持订阅者Context-绑定连接,例如,与R2dbcTransactionManager.spring-doc.cadn.net.cn

SingleConnectionFactory

SingleConnectionFactoryclass 是DelegatingConnectionFactory将单个Connection每次使用后不会关闭。spring-doc.cadn.net.cn

如果任何客户端代码调用close假设池连接(如使用 持久化工具),您应该将suppressClose属性设置为true.此设置 返回包装物理连接的紧密抑制代理。请注意,您可以 不再将此内容强制转换为原生Connection或类似对象。spring-doc.cadn.net.cn

SingleConnectionFactory主要是一个测试类,可用于特定要求 例如流水线(如果您的 R2DBC 驱动程序允许此类使用)。 与池ConnectionFactory,它始终重复使用相同的连接,避免了 过度创建物理连接。spring-doc.cadn.net.cn

TransactionAwareConnectionFactoryProxy

TransactionAwareConnectionFactoryProxy是目标的代理ConnectionFactory. 代理包装该目标ConnectionFactory以增加对 Spring 管理的事务的认识。spring-doc.cadn.net.cn

如果您使用未集成的 R2DBC 客户端,则需要使用此类 与 Spring 的 R2DBC 支持。在这种情况下,您仍然可以使用此客户端,并且 同时,让这个客户端参与 Spring 管理的事务。一般是 最好集成具有适当访问权限的 R2DBC 客户端ConnectionFactoryUtils用于资源管理。

请参阅TransactionAwareConnectionFactoryProxyjavadoc 了解更多详情。spring-doc.cadn.net.cn

R2dbcTransactionManager

R2dbcTransactionManagerclass 是一个ReactiveTransactionManager实现 单个 R2DBCConnectionFactory.它绑定一个 R2DBCConnection从指定的ConnectionFactory给订阅者Context,可能允许一个订阅者Connection对于每个ConnectionFactory.spring-doc.cadn.net.cn

检索 R2DBC 需要应用程序代码Connection通过ConnectionFactoryUtils.getConnection(ConnectionFactory),而不是 R2DBC 的标准ConnectionFactory.create().所有框架类(例如DatabaseClient)使用这个 隐含的策略。如果不与事务管理器一起使用,则查找策略的行为 一模一样ConnectionFactory.create()因此可以在任何情况下使用。spring-doc.cadn.net.cn