Persisting Entities
R2dbcEntityTemplate
是 Spring Data R2DBC 的中央入口点。它提供了直接面向实体的方法以及更窄的、流畅的界面,用于典型的临时用例,例如查询、插入、更新和删除数据。
R2dbcEntityTemplate
is the central entrypoint for Spring Data R2DBC.
It provides direct entity-oriented methods and a more narrow, fluent interface for typical ad-hoc use-cases, such as querying, inserting, updating, and deleting data.
入口点(insert()
、select()
、update()
和其他)遵循基于要运行的操作的自然命名模式。从入口点开始,API 被设计为仅提供上下文相关的方法,这些方法导致创建和运行 SQL 语句的终止方法。Spring Data R2DBC 使用 R2dbcDialect
抽象来确定绑定标记、分页支持和底层驱动程序本机支持的数据类型。
The entry points (insert()
, select()
, update()
, and others) follow a natural naming schema based on the operation to be run.
Moving on from the entry point, the API is designed to offer only context-dependent methods that lead to a terminating method that creates and runs a SQL statement.
Spring Data R2DBC uses a R2dbcDialect
abstraction to determine bind markers, pagination support and the data types natively supported by the underlying driver.
所有终端方法始终返回一个表示所需操作的 |
All terminal methods return always a |
Methods for Inserting and Updating Entities
R2dbcEntityTemplate
上有几种方便的方法,用于保存和插入你的对象。为了对转换过程进行更细粒度的控制,你可以使用 R2dbcCustomConversions
注册 Spring 转换器,例如 Converter<Person, OutboundRow>
和 Converter<Row, Person>
。
There are several convenient methods on R2dbcEntityTemplate
for saving and inserting your objects.
To have more fine-grained control over the conversion process, you can register Spring converters with R2dbcCustomConversions
— for example Converter<Person, OutboundRow>
and Converter<Row, Person>
.
使用保存操作的简单情况是保存一个 POJO。在这种情况下,表名由类的名称(不完全限定)确定。你还可以使用特定集合名称调用保存操作。你可以使用映射元数据覆盖存储对象的集合。
The simple case of using the save operation is to save a POJO. In this case, the table name is determined by name (not fully qualified) of the class. You may also call the save operation with a specific collection name. You can use mapping metadata to override the collection in which to store the object.
插入或保存时,如果未设置 Id
属性,则假设其值将由数据库自动生成。因此,对于自动生成,类中 Id
属性或字段的类型必须为 Long
或 Integer
。
When inserting or saving, if the Id
property is not set, the assumption is that its value will be auto-generated by the database.
Consequently, for auto-generation the type of the Id
property or field in your class must be a Long
, or Integer
.
以下示例演示如何插入行并检索其内容:
The following example shows how to insert a row and retrieving its contents:
R2dbcEntityTemplate
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
以下插入和更新操作可用:
The following insert and update operations are available:
也有一组类似的插入操作可用:
A similar set of insert operations is also available:
-
Mono<T>
insert(T objectToSave)
: Insert the object to the default table. -
Mono<T>
update(T objectToSave)
: Insert the object to the default table.
表名可以通过使用流畅的 API 来自定义。
Table names can be customized by using the fluent API.
Selecting Data
R2dbcEntityTemplate
上的 select(…)
和 selectOne(…)
方法用于从表中选择数据。这两种方法都会采用一个 <<`Query`,r2dbc.datbaseclient.fluent-api.criteria>> 对象,此对象定义字段投影、WHERE
子句、ORDER BY
子句以及限制/偏移分页。限制/偏移功能对于应用程序是透明的,无论底层数据库如何。xref:r2dbc/getting-started.adoc#r2dbc.dialects[R2dbcDialect
抽象支持此功能,以满足不同 SQL 风味之间的差异。
The select(…)
and selectOne(…)
methods on R2dbcEntityTemplate
are used to select data from a table.
Both methods take a <<`Query`,r2dbc.datbaseclient.fluent-api.criteria>> object that defines the field projection, the WHERE
clause, the ORDER BY
clause and limit/offset pagination.
Limit/offset functionality is transparent to the application regardless of the underlying database.
This functionality is supported by the R2dbcDialect
abstraction to cater for differences between the individual SQL flavors.
R2dbcEntityTemplate
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
Fluent API
本节介绍流畅 API 的用法。请考虑以下简单的查询:
This section explains the fluent API usage. Consider the following simple query:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | Using Person with the select(…) method maps tabular results on Person result objects. |
2 | Fetching all() rows returns a Flux<Person> without limiting results. |
以下示例声明了一个更复杂的查询,它按名称指定表名、WHERE
条件和 ORDER BY
子句:
The following example declares a more complex query that specifies the table name by name, a WHERE
condition, and an ORDER BY
clause:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | Selecting from a table by name returns row results using the given domain type. |
2 | The issued query declares a WHERE condition on firstname and lastname columns to filter results. |
3 | Results can be ordered by individual column names, resulting in an ORDER BY clause. |
4 | Selecting the one result fetches only a single row.
This way of consuming rows expects the query to return exactly a single result.
Mono emits a IncorrectResultSizeDataAccessException if the query yields more than a single result. |
您可以通过提供目标类型(通过“ |
You can directly apply Projections to results by providing the target type via |
可以通过以下终止方法在检索单个实体和检索多个实体之间切换:
You can switch between retrieving a single entity and retrieving multiple entities through the following terminating methods:
-
first()
: Consume only the first row, returning aMono
. The returnedMono
completes without emitting an object if the query returns no results. -
one()
: Consume exactly one row, returning aMono
. The returnedMono
completes without emitting an object if the query returns no results. If the query returns more than one row,Mono
completes exceptionally emittingIncorrectResultSizeDataAccessException
. -
all()
: Consume all returned rows returning aFlux
. -
count()
: Apply a count projection returningMono<Long>
. -
exists()
: Return whether the query yields any rows by returningMono<Boolean>
.
可以使用 select()
入口点来表达 SELECT
查询。生成的 SELECT
查询支持常用的子句(WHERE
和 ORDER BY
)并支持分页。流畅 API 样式允许你链接多个方法,同时具有易于理解的代码。为了提高可读性,可以使用静态导入,这样你无需使用 'new' 关键字来创建 Criteria
实例。
You can use the select()
entry point to express your SELECT
queries.
The resulting SELECT
queries support the commonly used clauses (WHERE
and ORDER BY
) and support pagination.
The fluent API style let you chain together multiple methods while having easy-to-understand code.
To improve readability, you can use static imports that let you avoid using the 'new' keyword for creating Criteria
instances.
Methods for the Criteria Class
Criteria
类提供以下方法,所有方法都对应于 SQL 运算符:
The Criteria
class provides the following methods, all of which correspond to SQL operators:
-
Criteria
and(String column)
: Adds a chainedCriteria
with the specifiedproperty
to the currentCriteria
and returns the newly created one. -
Criteria
or(String column)
: Adds a chainedCriteria
with the specifiedproperty
to the currentCriteria
and returns the newly created one. -
Criteria
greaterThan(Object o)
: Creates a criterion by using the>
operator. -
Criteria
greaterThanOrEquals(Object o)
: Creates a criterion by using the>=
operator. -
Criteria
in(Object… o)
: Creates a criterion by using theIN
operator for a varargs argument. -
Criteria
in(Collection<?> collection)
: Creates a criterion by using theIN
operator using a collection. -
Criteria
is(Object o)
: Creates a criterion by using column matching (property = value
). -
Criteria
isNull()
: Creates a criterion by using theIS NULL
operator. -
Criteria
isNotNull()
: Creates a criterion by using theIS NOT NULL
operator. -
Criteria
lessThan(Object o)
: Creates a criterion by using the<
operator. -
Criteria
lessThanOrEquals(Object o)
: Creates a criterion by using the⇐
operator. -
Criteria
like(Object o)
: Creates a criterion by using theLIKE
operator without escape character processing. -
Criteria
not(Object o)
: Creates a criterion by using the!=
operator. -
Criteria
notIn(Object… o)
: Creates a criterion by using theNOT IN
operator for a varargs argument. -
Criteria
notIn(Collection<?> collection)
: Creates a criterion by using theNOT IN
operator using a collection.
你可以对 SELECT
、UPDATE
和 DELETE
查询使用 Criteria
。
You can use Criteria
with SELECT
, UPDATE
, and DELETE
queries.
Inserting Data
可以使用 insert()
入口点来插入数据。
You can use the insert()
entry point to insert data.
请考虑以下简单的类型化插入操作:
Consider the following simple typed insert operation:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | Using Person with the into(…) method sets the INTO table, based on mapping metadata.
It also prepares the insert statement to accept Person objects for inserting. |
2 | Provide a scalar Person object.
Alternatively, you can supply a Publisher to run a stream of INSERT statements.
This method extracts all non-null values and inserts them. |
Updating Data
可以使用 update()
入口点来更新行。更新数据以通过接受指定要更新的表的 Update
指定赋值开始。它还接受 Query
来创建一个 WHERE
子句。
You can use the update()
entry point to update rows.
Updating data starts by specifying the table to update by accepting Update
specifying assignments.
It also accepts Query
to create a WHERE
clause.
请考虑以下简单的类型化更新操作:
Consider the following simple typed update operation:
Person modified = …
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | Update Person objects and apply mapping based on mapping metadata. |
2 | Set a different table name by calling the inTable(…) method. |
3 | Specify a query that translates into a WHERE clause. |
4 | Apply the Update object.
Set in this case age to 42 and return the number of affected rows. |
Deleting Data
可以使用 delete()
入口点来删除行。删除数据以指定要删除的表开始,并可以接受 Criteria
以创建 WHERE
子句(如果需要)。
You can use the delete()
entry point to delete rows.
Removing data starts with a specification of the table to delete from and, optionally, accepts a Criteria
to create a WHERE
clause.
请考虑以下简单的插入操作:
Consider the following simple insert operation:
Unresolved include directive in modules/ROOT/pages/r2dbc/entity-persistence.adoc - include::example$r2dbc/R2dbcEntityTemplateSnippets.java[]
1 | Delete Person objects and apply mapping based on mapping metadata. |
2 | Set a different table name by calling the from(…) method. |
3 | Specify a query that translates into a WHERE clause. |
4 | Apply the delete operation and return the number of affected rows. |
使用存储库时,可以使用 ReactiveCrudRepository.save(…)
方法来保存实体。如果实体是新的,这会导致插入该实体。
Using Repositories, saving an entity can be performed with the ReactiveCrudRepository.save(…)
method.
If the entity is new, this results in an insert for the entity.
如果实体不是新的,则对其进行更新。请注意,实例是否新是实例状态的一部分。
If the entity is not new, it gets updated. Note that whether an instance is new is part of the instance’s state.
这种方法有一些明显的缺点。如果只有几个被引用的实体发生了实际更改,那么删除和插入就是浪费。虽然这个过程可以并且可能得到改善,但 Spring Data R2DBC 能够提供的东西有一些限制。它不知道聚合的先前状态。因此,任何更新过程都必须始终获取数据库中找到的任何内容,并确保将其转换为传递给保存方法的实体状态。 |
This approach has some obvious downsides. If only few of the referenced entities have been actually changed, the deletion and insertion is wasteful. While this process could and probably will be improved, there are certain limitations to what Spring Data R2DBC can offer. It does not know the previous state of an aggregate. So any update process always has to take whatever it finds in the database and make sure it converts it to whatever is the state of the entity passed to the save method. |
ID Generation
ID Generation
Spring Data 使用标识符属性识别实体。实体的 ID 必须使用 Spring Data 的 @Id
注解进行注释。
Spring Data uses the identifer property to identify entities.
The ID of an entity must be annotated with Spring Data’s @Id
annotation.
当你的数据库为 ID 列拥有自动增加列时,在将其插入数据库之后,生成的值会设置在实体中。
When your database has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database.
当实体是新的且标识符值默认为其初始值时,Spring Data 不会尝试插入标识符列的值。对于原始类型是 0
,对于标识符属性使用 Long
等数字包装器类型则为 null
。
Spring Data does not attempt to insert values of identifier columns when the entity is new and the identifier value defaults to its initial value.
That is 0
for primitive types and null
if the identifier property uses a numeric wrapper type such as Long
.
Entity State Detection 详细解释了检测实体是新实体还是它存在于数据库中的策略。
Entity State Detection explains in detail the strategies to detect whether an entity is new or whether it is expected to exist in your database.
一个重要的约束是,在保存一个实体之后,实体不能再是新的。请注意,实体是否为新实体是实体状态的一部分。对于自动增加列,这是自动发生的,因为 ID 由 Spring Data 使用 ID 列中的值进行设置。
One important constraint is that, after saving an entity, the entity must not be new anymore. Note that whether an entity is new is part of the entity’s state. With auto-increment columns, this happens automatically, because the ID gets set by Spring Data with the value from the ID column.
Optimistic Locking
Spring Data 通过聚合根上使用 @Version
注释的数字属性来支持乐观锁定。每当 Spring Data 保存具有此类版本属性的聚合时,都会发生两件事:
Spring Data supports optimistic locking by means of a numeric attribute that is annotated with
@Version
on the aggregate root.
Whenever Spring Data saves an aggregate with such a version attribute two things happen:
-
The update statement for the aggregate root will contain a where clause checking that the version stored in the database is actually unchanged.
-
If this isn’t the case an
OptimisticLockingFailureException
will be thrown.
另外,版本属性在实体和数据库中都会增加,因此,并发操作会注意到更改,并在适用的情况下抛出`OptimisticLockingFailureException` ,如上所述。
Also, the version attribute gets increased both in the entity and in the database so a concurrent action will notice the change and throw an OptimisticLockingFailureException
if applicable as described above.
此过程也适用于插入新聚合,其中 null
或 0
版本指示一个新实例,而增加的实例随后将实例标记为不再是新的,这在对象构造期间生成 id 的情况下可以很好地发挥作用,例如在使用 UUID 时。
This process also applies to inserting new aggregates, where a null
or 0
version indicates a new instance and the increased instance afterwards marks the instance as not new anymore, making this work rather nicely with cases where the id is generated during object construction for example when UUIDs are used.
在删除期间,版本检查也会适用,但不会增加版本。
During deletes the version check also applies but no version is increased.
@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 | Initially insert row. version is set to 0 . |
2 | Load the just inserted row. version is still 0 . |
3 | Update the row with version = 0 .Set the lastname and bump version to 1 . |
4 | Try to update the previously loaded row that still has version = 0 .The operation fails with an OptimisticLockingFailureException , as the current version is 1 . |