跳到主要内容

持久化实体

Doubao 中英对照 Persisting Entities

保存聚合可使用 CrudRepository.save(…) 方法完成。如果聚合是新的,这将对聚合根执行一次插入,并随后对所有直接或间接引用的实体执行插入语句。

如果聚合根不是新的,则所有被引用的实体会被删除,聚合根会被更新,然后所有被引用的实体再次被插入。请注意,一个实例是否为新的,是该实例状态的一部分。

备注

这种方式有一些明显的缺点。如果只有少数被引用的实体实际发生了更改,那么删除和插入就会造成浪费。虽然这个过程可以而且很可能会得到改进,但 Spring Data JDBC 能提供的能力存在一定的局限。它不知道一个聚合此前的状态。因此任何更新过程都必须以数据库中现有的内容为准,并确保将其转换为传入 save 方法的实体所代表的状态。

有关更多详细信息,另请参阅实体状态检测。

加载聚合​

Spring Data JDBC 提供两种加载聚合的方式:

  1. 传统方式(在 3.2 版本之前也是唯一的方式)非常简单:每次查询都加载聚合根,无论该查询是基于 CrudRepository 方法、派生查询还是注解查询。如果聚合根引用了其他实体,这些实体会通过单独的语句加载。

  2. Spring Data JDBC 3.2 允许使用_单查询加载_(Single Query Loading)。借助它,一条 SQL 查询即可完整加载任意数量的聚合。这应该会显著提高效率,尤其是对于由众多实体组成的复杂聚合。

    目前,单查询加载在以下方面受到限制:

    1. 聚合不能包含嵌套集合,这包括 Map。计划在未来移除这一限制。

    2. 聚合不能使用 AggregateReference 或内嵌实体。计划在未来移除这一限制。

    3. 数据库方言必须支持该功能。在 Spring Data JDBC 提供的方言中,除 H2 和 HSQL 外都支持该功能。H2 和 HSQL 不支持分析函数(即窗口函数)。

    4. 它仅适用于 CrudRepository 中的 find 方法,不适用于派生查询,也不适用于注解查询。计划在未来移除这一限制。

    5. 单查询加载需要在 JdbcMappingContext 中通过调用 setSingleQueryLoadingEnabled(true) 来启用。

如果任一条件不满足,Spring Data JDBC 将回退到加载聚合的默认方式。

备注

单查询加载应被视为实验性功能。我们欢迎您反馈它在您的使用场景中的效果。

备注

虽然单查询加载可以缩写为 SQL,但我们强烈建议不要这样做,因为几乎必然会与结构化查询语言(Structured Query Language)产生混淆。

ID 生成​

Spring Data 使用标识符属性来标识实体,即用于查找实体或生成针对特定行的语句。实体的 ID 必须使用 Spring Data 的 @Id 注解进行标注。

如果您的数据库在 ID 列上设有自增列,那么在将实体插入数据库后,生成的值会被设置到实体中。

如果您额外用 @Sequence 注解标注标识符属性,那么在底层 Dialect 支持序列时,将使用数据库序列来获取 ID 值。

否则,当实体为新的且标识符值默认为初始值时,Spring Data 不会尝试插入标识符列的值。对于基本类型,该初始值为 0;如果标识符属性使用了数值包装类型(例如 Long),则为 null。

实体状态检测详细解释了检测实体是新的、还是预期已存在于数据库中的策略。

一个重要的约束是:保存实体之后,该实体不得再被视为新的。请注意,一个实体是否为新的是该实体状态的一部分。对于自增列,这会自动发生,因为 Spring Data 会用 ID 列中的值设置 ID。

模板 API​

作为仓库的替代方案,Spring Data JDBC 提供 JdbcAggregateTemplate,作为在关系型数据库中加载和持久化实体的更直接手段。在很大程度上,仓库正是使用 JdbcAggregateTemplate 来实现其功能的。

本节仅重点介绍 JdbcAggregateTemplate 中最值得关注的部分。更完整的概述请参阅 JdbcAggregateTemplate 的 JavaDoc。

访问 JdbcAggregateTemplate​

JdbcAggregateTemplate 旨在用作 Spring bean。如果您的应用程序已配置为包含 Spring Data JDBC,那么您可以在任意 Spring bean 中声明对 JdbcAggregateTemplate 的依赖,Spring Framework 会注入一个配置好的实例。

这包括您用于为 Spring Data 仓库实现自定义方法的片段,从而让您可以使用 JdbcAggregateTemplate 来自定义和扩展您的仓库。

持久化​

JdbcAggregateTemplate 提供三类用于持久化实体的方法:save、insert 和 update。每类方法都有两种形式:一种作用于单个聚合,名称如上所述;另一种带有 All 后缀,作用于 Iterable。

save 的作用与仓库中的同名方法相同。

insert 和 update 会跳过「实体是否为新的」这一判断,并如其名称所表明的那样,假定聚合是新的或已存在的。

Upsert(更新或插入)​

JdbcAggregateTemplate 提供了 upsert(instance) 操作,适用于您需要在不预先检查是否存在的情况下,通过单次数据库往返来持久化一个聚合根的场景。该操作执行原生的数据库 upsert:当不存在与相关主键匹配的行时插入该行,否则更新该行。

请注意,该操作要求实体实例必须具有标识符值。此外还需要数据库方言支持:该功能适用于 PostgreSQL、H2、MySQL、MariaDB、SQL Server、Oracle、DB2 和 HSQLDB。

备注

Upsert 目前不支持乐观锁。

查询​

JdbcAggregateTemplate 提供了大量用于查询聚合以及聚合集合的方法。其中有一类方法需要特别注意,那就是以 Query 作为参数的方法。它们允许执行以编程方式构建的查询,如下所示:

template.findOne(query(where("name").is("Gandalf")), Person.class);
java

由 query 方法返回的 Query 定义了要查询的列列表、where 子句(通过 CriteriaDefinition)以及 limit 和 offset 子句的规范。有关 Query 类的详细信息,请参阅其 JavaDoc。

Criteria 类(where 是它的一个静态成员)提供了 CriteriaDefinition 的实现,后者表示查询的 where 子句。

Criteria 类的方法​

Criteria 类提供以下方法,它们均对应于 SQL 运算符:

  • Criteria and (String column):在当前 Criteria 上添加一个带有指定 property 的链式 Criteria,并返回新创建的 Criteria。

  • Criteria or (String column):在当前 Criteria 上添加一个带有指定 property 的链式 Criteria,并返回新创建的 Criteria。

  • 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 运算符创建一个条件。

乐观锁​

Spring Data 通过在聚合根上使用 @Version 注解标注的数值属性来支持乐观锁。每当 Spring Data 保存一个带有此版本属性的聚合时,会发生两件事:

  • 聚合根的 update 语句将包含一个 where 子句,用于检查数据库中存储的版本实际上未发生改变。

  • 如果不是这种情况,将抛出 OptimisticLockingFailureException。

此外,版本属性会在实体中和数据库中同时增加,因此并发操作会注意到这一变化,并在如上所述的适用情况下抛出 OptimisticLockingFailureException。

这个过程同样适用于插入新聚合:其中 null 或 0 版本表示新实例,随后增加后的版本会将该实例标记为不再是新的,这使得该机制与「在对象构造期间生成 id」的场景(例如使用 UUID 时)配合得相当好。

在删除期间同样会进行版本检查,但版本不会增加。