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
。
以下示例演示如何插入行并检索其内容:
R2dbcEntityTemplate
Unresolved 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 风味之间的差异。
R2dbcEntityTemplate
Unresolved 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 结果对象中。 |
2 | 获取 all() 行会返回一个 Flux<Person> ,且不限制结果。 |
以下示例声明了一个更复杂的查询,它按名称指定表名、WHERE
条件和 ORDER BY
子句:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | 根据名称选择表格会使用给定的域名类型返回行结果。 |
2 | 发出的查询在 firstname 和 lastname 列中声明了一个 WHERE 条件来过滤结果。 |
3 | 可以使用各个列名对结果进行排序,从而生成一个 ORDER BY 子句。 |
4 | 选择一个结果只能获取一个单一的行。这种消费行的方式预期查询只返回单个结果。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 运算符:
-
Criteria
and(String column)
:向当前Criteria
添加一个链式的Criteria
,其中包含指定的property
,并返回新创建的对象。 -
Criteria
or(String column)
:向当前Criteria
添加一个链式的Criteria
,其中包含指定的property
,并返回新创建的对象。 -
Criteria
greaterThan(Object o)
:使用>
操作符创建标准。 -
Criteria
greaterThanOrEquals(Object o)
:使用>=
操作符创建标准。 -
Criteria
in(Object…​ o)
:使用IN
操作符为一个可变参数创建标准。 -
Criteria
in(Collection<?> collection)
:使用IN
操作符使用一个集合创建标准。 -
Criteria
is(Object o)
:使用列匹配(property = value
)创建标准。 -
Criteria
isNull()
:使用IS NULL
操作符创建标准。 -
Criteria
isNotNull()
:使用IS NOT NULL
操作符创建标准。 -
Criteria
lessThan(Object o)
:使用<
操作符创建标准。 -
Criteria
lessThanOrEquals(Object o)
:使用⇐
操作符创建标准。 -
Criteria
like(Object o)
:使用LIKE
操作符创建标准而不进行转义字符处理。 -
Criteria
not(Object o)
:使用!=
操作符创建标准。 -
Criteria
notIn(Object…​ o)
:使用NOT IN
操作符为一个可变参数创建标准。 -
Criteria
notIn(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 对象进行插入。 |
2 | 提供标量 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 对象并基于映射元数据应用映射。 |
2 | 通过调用 inTable(…) 方法设置不同的表名。 |
3 | 指定转换为 WHERE 子句的查询。 |
4 | 应用 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 对象并基于映射元数据应用映射。 |
2 | 通过调用 from(…) 方法设置不同的表名。 |
3 | 指定转换为 WHERE 子句的查询。 |
4 | 应用删除操作并返回受影响的行数。 |
使用存储库时,可以使用 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 。 |
2 | 加载刚插入的行。version 仍然是 0 。 |
3 | 使用 version = 0 更新该行。设置 lastname 并将 version 提升到 1 。 |
4 | 尝试使用仍然具有 version = 0 的先前加载的行。由于当前 version 是 1 ,因此此操作将失败,并出现 OptimisticLockingFailureException 。 |