持久化实体
R2dbcEntityTemplate
是 Spring Data R2DBC 的中心入口点。
它为典型的临时用例(例如查询、插入、更新和删除数据)提供了直接面向实体的方法和更窄、更流畅的界面。
入口点 (insert()
,select()
,update()
等)遵循基于要运行的作的自然命名模式。
从入口点开始,API 旨在仅提供依赖于上下文的方法,这些方法会导致创建和运行 SQL 语句的终止方法。
Spring Data R2DBC 使用R2dbcDialect
抽象,用于确定绑定标记、分页支持以及基础驱动程序本机支持的数据类型。
所有终端方法始终返回一个Publisher 类型,表示所需的作。
实际语句在订阅时发送到数据库。 |
插入和更新实体的方法
有几种方便的方法R2dbcEntityTemplate
用于保存和插入您的对象。
为了对转换过程进行更细粒度的控制,你可以在R2dbcCustomConversions
——例如Converter<Person, OutboundRow>
和Converter<Row, Person>
.
使用保存作的简单情况是保存一个 POJO。 在这种情况下,表名由类的名称(非完全限定)确定。 您还可以使用特定的集合名称调用保存作。 可以使用映射元数据来替代要存储对象的集合。
插入或保存时,如果Id
属性未设置,则假设其值将由数据库自动生成。
因此,对于自动生成,类型Id
属性或字段必须是Long
或Integer
.
以下示例演示如何插入行并检索其内容:
R2dbcEntityTemplate
Person person = new Person("John", "Doe");
Mono<Person> saved = template.insert(person);
Mono<Person> loaded = template.selectOne(query(where("firstname").is("John")),
Person.class);
可以使用以下插入和更新作:
还提供了一组类似的插入作:
-
Mono<T>
插入(T objectToSave)
:将对象插入到默认表中。 -
Mono<T>
更新(T objectToSave)
:将对象插入到默认表中。
可以使用 fluent API 自定义表名。
选择数据
这select(…)
和selectOne(…)
方法R2dbcEntityTemplate
用于从表中选择数据。
这两种方法都采用Query
定义字段投影的对象,WHERE
子句中,则ORDER BY
子句和限制/偏移分页。
限制/偏移功能对应用程序是透明的,而不管底层数据库如何。
此功能由R2dbcDialect
抽象化以解决各个 SQL 风格之间的差异。
R2dbcEntityTemplate
Flux<Person> loaded = template.select(query(where("firstname").is("John")),
Person.class);
流畅的 API
本节介绍流畅的 API 用法。 考虑以下简单查询:
Flux<Person> people = template.select(Person.class) (1)
.all(); (2)
1 | 用Person 使用select(…) 方法将表格结果映射到Person result 对象。 |
2 | 取all() rows 返回一个Flux<Person> 而不限制结果。 |
以下示例声明了一个更复杂的查询,该查询按名称指定表名,即WHERE
条件,以及ORDER BY
第:
Mono<Person> first = template.select(Person.class) (1)
.from("other_person")
.matching(query(where("firstname").is("John") (2)
.and("lastname").in("Doe", "White"))
.sort(by(desc("id")))) (3)
.one(); (4)
1 | 按名称从表中选择将使用给定的域类型返回行结果。 |
2 | 发出的查询声明WHERE 条件firstname 和lastname 列来筛选结果。 |
3 | 结果可以按单个列名称排序,从而产生ORDER BY 第。 |
4 | 选择一个结果仅获取一行。
这种使用行的方式期望查询只返回一个结果。Mono 发出IncorrectResultSizeDataAccessException 如果查询产生多个结果。 |
您可以通过以下方式提供目标类型,直接将投影应用于结果select(Class<?>) . |
您可以通过以下终止方法在检索单个实体和检索多个实体之间切换:
-
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
实例。
Criteria类的方法
这Criteria
class 提供了以下方法,所有这些方法都对应于 SQL 运算符:
-
Criteria
和(String column)
:添加一个链式的Criteria
使用指定的property
到当前Criteria
并返回新创建的。 -
Criteria
或(String column)
:添加一个链式的Criteria
使用指定的property
到当前Criteria
并返回新创建的。 -
Criteria
大于(Object o)
:使用运算符创建条件。>
-
Criteria
大于或等于(Object o)
:使用运算符创建条件。>=
-
Criteria
在(Object… o)
:使用IN
varargs 参数的运算符。 -
Criteria
在(Collection<?> collection)
:使用IN
运算符。 -
Criteria
是(Object o)
:使用列匹配 (property = value
). -
Criteria
isNull :使用()
IS NULL
算子。 -
Criteria
isNotNull :使用()
IS NOT NULL
算子。 -
Criteria
小于(Object o)
:使用运算符创建条件。<
-
Criteria
小于或等于(Object o)
:使用⇐
算子。 -
Criteria
喜欢(Object o)
:使用LIKE
运算符,无需转义字符处理。 -
Criteria
不(Object o)
:使用!=
算子。 -
Criteria
notIn(Object… o)
:使用NOT IN
varargs 参数的运算符。 -
Criteria
notIn(Collection<?> collection)
:使用NOT IN
运算符。 您可以使用Criteria
跟SELECT
,UPDATE
和DELETE
查询。
插入数据
您可以使用insert()
插入数据的入口点。
考虑以下简单的类型插入作:
Mono<Person> insert = template.insert(Person.class) (1)
.using(new Person("John", "Doe")); (2)
1 | 用Person 使用into(…) 方法将INTO 表,基于映射元数据。
它还准备insert语句以接受Person 用于插入的对象。 |
2 | 提供标量Person 对象。
或者,您可以提供Publisher 运行INSERT 语句。
该方法提取所有非null 值并插入它们。 |
更新数据
您可以使用update()
更新行的入口点。
更新数据首先通过指定要更新的表,方法是接受Update
指定分配。
它还接受Query
创建WHERE
第。
考虑以下简单的类型化更新作:
Person modified = …
Mono<Long> update = template.update(Person.class) (1)
.inTable("other_table") (2)
.matching(query(where("firstname").is("John"))) (3)
.apply(update("age", 42)); (4)
1 | 更新Person 对象并基于映射元数据应用映射。 |
2 | 通过调用inTable(…) 方法。 |
3 | 指定转换为WHERE 第。 |
4 | 应用Update 对象。
在这种情况下设置age 自42 并返回受影响的行数。 |
删除数据
您可以使用delete()
用于删除行的入口点。
删除数据从要从中删除的表的规范开始,并且(可选)接受Criteria
创建WHERE
第。
考虑以下简单的插入作:
Mono<Long> delete = template.delete(Person.class) (1)
.from("other_table") (2)
.matching(query(where("firstname").is("John"))) (3)
.all(); (4)
1 | 删除Person 对象并基于映射元数据应用映射。 |
2 | 通过调用from(…) 方法。 |
3 | 指定转换为WHERE 第。 |
4 | 应用删除作并返回受影响的行数。 |
使用存储库,可以使用ReactiveCrudRepository.save(…)
方法。
如果实体是新的,则会导致实体的插入。
如果实体不是新的,则会更新它。 请注意,实例是否为新实例是实例状态的一部分。
这种方法有一些明显的缺点。 如果实际上只有少数引用的实体被更改,则删除和插入是浪费的。 虽然这个过程可以而且可能会得到改进,但 Spring Data R2DBC 可以提供的功能存在某些限制。 它不知道聚合的先前状态。 因此,任何更新过程始终必须获取它在数据库中找到的任何内容,并确保将其转换为传递给 save 方法的实体的任何状态。 |
ID 生成
Spring Data 使用标识符属性来标识实体。
也就是说,查找这些或创建针对特定行的语句。
实体的 ID 必须使用 Spring Data 的@Id
注解。
当数据库具有 ID 列的自动递增列时,生成的值将在将其插入数据库后在实体中设置。
如果另外使用@Sequence
如果基础Dialect
支持序列。
否则,当实体是新的并且标识符值默认为其初始值时,Spring Data不会尝试插入标识符列的值。
那是0
对于原始类型和null
如果 identifier 属性使用数字包装器类型,例如Long
.
实体状态检测详细解释了检测实体是新实体还是数据库中是否应存在的策略。
一个重要的约束是,保存实体后,该实体不得再是新的。 请注意,实体是否为新实体是实体状态的一部分。 使用自动递增列,这会自动发生,因为 ID 是由 Spring Data 使用 ID 列中的值设置的。
乐观锁定
Spring Data 支持通过注释为@Version
在聚合根上。
每当 Spring Data 保存具有此类版本属性的聚合时,都会发生两件事:
-
聚合根的 update 语句将包含一个 where 子句,用于检查存储在数据库中的版本是否实际未更改。
-
如果不是这种情况,则
OptimisticLockingFailureException
将被扔出。
此外,在实体和数据库中都会增加 version 属性,因此并发作将注意到更改并抛出OptimisticLockingFailureException
如上所述,如果适用。
此过程也适用于插入新聚合,其中null
或0
version 表示一个新实例,之后增加的实例将该实例标记为不再是新的实例,这使得这在对象构造期间生成 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 .该作失败,并显示OptimisticLockingFailureException ,作为当前version 是1 . |