Persisting Entities
R2dbcEntityTemplate 是 Spring Data R2DBC 的中央入口点。它提供了直接面向实体的方法以及更窄的、流畅的界面,用于典型的临时用例,例如查询、插入、更新和删除数据。
入口点(insert()、select()、update() 和其他)遵循基于要运行的操作的自然命名模式。从入口点开始,API 被设计为仅提供上下文相关的方法,这些方法导致创建和运行 SQL 语句的终止方法。Spring Data R2DBC 使用 R2dbcDialect 抽象来确定绑定标记、分页支持和底层驱动程序本机支持的数据类型。
|
所有终端方法始终返回一个表示所需操作的 |
Methods for Inserting and Updating Entities
R2dbcEntityTemplate 上有几种方便的方法,用于保存和插入你的对象。为了对转换过程进行更细粒度的控制,你可以使用 R2dbcCustomConversions 注册 Spring 转换器,例如 Converter<Person, OutboundRow> 和 Converter<Row, Person>。
使用保存操作的简单情况是保存一个 POJO。在这种情况下,表名由类的名称(不完全限定)确定。你还可以使用特定集合名称调用保存操作。你可以使用映射元数据覆盖存储对象的集合。
插入或保存时,如果未设置 Id 属性,则假设其值将由数据库自动生成。因此,对于自动生成,类中 Id 属性或字段的类型必须为 Long 或 Integer。
以下示例演示如何插入行并检索其内容:
R2dbcEntityTemplateUnresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
以下插入和更新操作可用:
也有一组类似的插入操作可用:
-
Mono<T>insert(T objectToSave):将对象插入默认表。 -
Mono<T>update(T objectToSave):插入对象到默认表中。
表名可以通过使用流畅的 API 来自定义。
Selecting Data
R2dbcEntityTemplate 上的 select(…) 和 selectOne(…) 方法用于从表中选择数据。这两种方法都会采用一个 <<`Query`,r2dbc.datbaseclient.fluent-api.criteria>> 对象,此对象定义字段投影、WHERE 子句、ORDER BY 子句以及限制/偏移分页。限制/偏移功能对于应用程序是透明的,无论底层数据库如何。xref:r2dbc/getting-started.adoc#r2dbc.dialects[R2dbcDialect 抽象支持此功能,以满足不同 SQL 风味之间的差异。
R2dbcEntityTemplateUnresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
Fluent API
本节介绍流畅 API 的用法。请考虑以下简单的查询:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
<1> 使用 `Person` 及 `select(…)` 方法将表格结果映射到 `Person` 结果对象中。 <1> 获取 `all()` 行会返回一个 `Flux&lt;Person&gt;`,且不限制结果。
以下示例声明了一个更复杂的查询,它按名称指定表名、WHERE 条件和 ORDER BY 子句:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
<1> 根据名称选择表格会使用给定的域名类型返回行结果。 <1> 发出的查询在 `firstname` 和 `lastname` 列中声明了一个 `WHERE` 条件来过滤结果。 <1> 可以使用各个列名对结果进行排序,从而生成一个 `ORDER BY` 子句。 <1> 选择一个结果只能获取一个单一的行。这种消费行的方式预期查询只返回单个结果。`Mono` 在查询产生多于单个结果时会发出一个 `IncorrectResultSizeDataAccessException`。
|
您可以通过提供目标类型(通过“ |
可以通过以下终止方法在检索单个实体和检索多个实体之间切换:
-
first():只消费第一行,返回一个Mono。如果查询没有返回结果,则返回的Mono完成时不会发出对象。 -
one():只消费一行,返回一个Mono。如果查询没有返回结果,则返回的Mono完成时不会发出对象。如果查询返回多于一行,则Mono完成时会发出一个IncorrectResultSizeDataAccessException异常。 -
all():消费所有返回行,返回一个Flux。 -
count():应用一个计数投影,返回Mono<Long>。 -
exists():通过返回Mono<Boolean>来返回查询是否产生任何行。
可以使用 select() 入口点来表达 SELECT 查询。生成的 SELECT 查询支持常用的子句(WHERE 和 ORDER BY)并支持分页。流畅 API 样式允许你链接多个方法,同时具有易于理解的代码。为了提高可读性,可以使用静态导入,这样你无需使用 'new' 关键字来创建 Criteria 实例。
Methods for the Criteria Class
Criteria 类提供以下方法,所有方法都对应于 SQL 运算符:
-
Criteriaand(String column):向当前Criteria添加一个链式的Criteria,其中包含指定的property,并返回新创建的对象。 -
Criteriaor(String column):向当前Criteria添加一个链式的Criteria,其中包含指定的property,并返回新创建的对象。 -
CriteriagreaterThan(Object o):使用>操作符创建标准。 -
CriteriagreaterThanOrEquals(Object o):使用>=操作符创建标准。 -
Criteriain(Object…​ o):使用IN操作符为一个可变参数创建标准。 -
Criteriain(Collection<?> collection):使用IN操作符使用一个集合创建标准。 -
Criteriais(Object o):使用列匹配(property = value)创建标准。 -
CriteriaisNull():使用IS NULL操作符创建标准。 -
CriteriaisNotNull():使用IS NOT NULL操作符创建标准。 -
CriterialessThan(Object o):使用<操作符创建标准。 -
CriterialessThanOrEquals(Object o):使用⇐操作符创建标准。 -
Criterialike(Object o):使用LIKE操作符创建标准而不进行转义字符处理。 -
Criterianot(Object o):使用!=操作符创建标准。 -
CriterianotIn(Object…​ o):使用NOT IN操作符为一个可变参数创建标准。 -
CriterianotIn(Collection<?> collection):使用NOT IN操作符使用一个集合创建标准。
你可以对 SELECT、UPDATE 和 DELETE 查询使用 Criteria。
Inserting Data
可以使用 insert() 入口点来插入数据。
请考虑以下简单的类型化插入操作:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
<1> 使用带有 `into(…)` 方法的 `Person` 基于映射元数据设置 `INTO` 表。它还准备插入语句以接受 `Person` 对象进行插入。 <1> 提供标量 `Person` 对象。或者,您可以提供 `Publisher`,以运行 `INSERT` 语句流。此方法会提取所有非 `null` 值并插入它们。
Updating Data
可以使用 update() 入口点来更新行。更新数据以通过接受指定要更新的表的 Update 指定赋值开始。它还接受 Query 来创建一个 WHERE 子句。
请考虑以下简单的类型化更新操作:
Person modified = …
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
<1> 更新 `Person` 对象并基于映射元数据应用映射。 <1> 通过调用 `inTable(…)` 方法设置不同的表名。 <1> 指定转换为 `WHERE` 子句的查询。 <1> 应用 `Update` 对象。在这种情况下,将 `age` 设置为 `42`,并返回受影响的行数。
Deleting Data
可以使用 delete() 入口点来删除行。删除数据以指定要删除的表开始,并可以接受 Criteria 以创建 WHERE 子句(如果需要)。
请考虑以下简单的插入操作:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
<1> 删除 `Person` 对象并基于映射元数据应用映射。 <1> 通过调用 `from(…)` 方法设置不同的表名。 <1> 指定转换为 `WHERE` 子句的查询。 <1> 应用删除操作并返回受影响的行数。
使用存储库时,可以使用 ReactiveCrudRepository.save(…) 方法来保存实体。如果实体是新的,这会导致插入该实体。
如果实体不是新的,则对其进行更新。请注意,实例是否新是实例状态的一部分。
|
这种方法有一些明显的缺点。如果只有几个被引用的实体发生了实际更改,那么删除和插入就是浪费。虽然这个过程可以并且可能得到改善,但 Spring Data R2DBC 能够提供的东西有一些限制。它不知道聚合的先前状态。因此,任何更新过程都必须始终获取数据库中找到的任何内容,并确保将其转换为传递给保存方法的实体状态。 |
ID Generation
ID Generation
Spring Data 使用标识符属性识别实体。实体的 ID 必须使用 Spring Data 的 @Id注解进行注释。
当你的数据库为 ID 列拥有自动增加列时,在将其插入数据库之后,生成的值会设置在实体中。
当实体是新的且标识符值默认为其初始值时,Spring Data 不会尝试插入标识符列的值。对于原始类型是 0,对于标识符属性使用 Long 等数字包装器类型则为 null。
Entity State Detection 详细解释了检测实体是新实体还是它存在于数据库中的策略。
一个重要的约束是,在保存一个实体之后,实体不能再是新的。请注意,实体是否为新实体是实体状态的一部分。对于自动增加列,这是自动发生的,因为 ID 由 Spring Data 使用 ID 列中的值进行设置。
Optimistic Locking
Spring Data 通过聚合根上使用 @Version注释的数字属性来支持乐观锁定。每当 Spring Data 保存具有此类版本属性的聚合时,都会发生两件事:
-
聚合根的更新语句将包含一个 where 子句,检查数据库中存储的版本是否实际上未更改。
-
如果不是这种情况,将抛出
OptimisticLockingFailureException。
另外,版本属性在实体和数据库中都会增加,因此,并发操作会注意到更改,并在适用的情况下抛出`OptimisticLockingFailureException` ,如上所述。
此过程也适用于插入新聚合,其中 null 或 0 版本指示一个新实例,而增加的实例随后将实例标记为不再是新的,这在对象构造期间生成 id 的情况下可以很好地发挥作用,例如在使用 UUID 时。
在删除期间,版本检查也会适用,但不会增加版本。
@Table
class Person {
@Id Long id;
String firstname;
String lastname;
@Version Long version;
}
R2dbcEntityTemplate template = …;
Mono<Person> daenerys = template.insert(new Person("Daenerys")); 1
Person other = template.select(Person.class)
.matching(query(where("id").is(daenerys.getId())))
.first().block(); 2
daenerys.setLastname("Targaryen");
template.update(daenerys); 3
template.update(other).subscribe(); // emits OptimisticLockingFailureException 4
<1> 最初插入行。`version` 设置为 `0`。 <1> 加载刚插入的行。`version` 仍然是 `0`。 <1> 使用 `version = 0` 更新该行。设置 `lastname` 并将 `version` 提升到 `1`。 <1> 尝试使用仍然具有 `version = 0` 的先前加载的行。由于当前 `version` 是 `1`,因此此操作将失败,并出现 `OptimisticLockingFailureException`。