持久化实体
保存聚合可以通过 CrudRepository.save(…) 方法完成。如果聚合是新的,这将导致插入聚合根,然后插入所有直接或间接引用的实体。
如果聚合根不是新的,所有引用的实体将被删除,聚合根将被更新,所有引用的实体将被重新插入。请注意,一个实例是否是新的,是该实例状态的一部分。
| 这种方法有一些明显的缺点。如果只有少数引用的实体被实际更改,那么删除和插入是浪费的。虽然这个过程可以并且可能会得到改进,但 Spring Data JDBC 所能提供的功能存在某些限制。它不知道聚合的先前状态。因此,任何更新过程都必须始终获取数据库中的内容,并确保将其转换为传递给 save 方法的实体的状态。 |
有关更多详细信息,另请参阅 实体状态检测。
加载聚合
Spring Data JDBC 提供两种加载聚合的方式
-
传统的方式,也是 3.2 版本之前唯一的方式,非常简单:每个查询都会加载聚合根,无论该查询是基于
CrudRepository方法、派生查询还是带注解的查询。如果聚合根引用了其他实体,这些实体将通过单独的语句加载。 -
Spring Data JDBC 3.2 允许使用单查询加载。通过此方式,可以使用单个 SQL 查询完全加载任意数量的聚合。这应该会显著提高效率,特别是对于由许多实体组成的复杂聚合。
目前,单查询加载在不同方面受到限制
-
聚合不能有嵌套集合,包括
Map。计划将来取消此限制。 -
聚合不得使用
AggregateReference或嵌入式实体。计划将来取消此限制。 -
数据库方言必须支持它。Spring Data JDBC 提供的方言中,除了 H2 和 HSQL 外,都支持此功能。H2 和 HSQL 不支持分析函数(即窗口函数)。
-
它仅适用于
CrudRepository中的 find 方法,不适用于派生查询和注解查询。计划将来取消此限制。 -
需要通过调用
setSingleQueryLoadingEnabled(true)在JdbcMappingContext中启用单查询加载。
-
如果任何条件不满足,Spring Data JDBC 将退回默认的聚合加载方法。
| 单查询加载被认为是实验性的。我们非常感谢您提供关于其使用情况的反馈。 |
| 虽然单查询加载可以缩写为 SQL,但我们强烈不建议这样做,因为几乎肯定会与结构化查询语言混淆。 |
ID 生成
Spring Data 使用标识符属性来标识实体。也就是说,查找这些实体或创建针对特定行的语句。实体的 ID 必须使用 Spring Data 的 @Id 注解进行标注。
当您的数据库的 ID 列具有自增功能时,生成的值在实体插入数据库后会被设置到实体中。
如果您还用 @Sequence 注解标识符属性,并且底层 Dialect 支持序列,则将使用数据库序列来获取 ID 的值。
否则,当实体是新的且标识符值默认为其初始值时,Spring Data 不会尝试插入标识符列的值。也就是说,对于基本类型是 0,如果标识符属性使用数字包装类型(例如 Long),则为 null。
实体状态检测详细解释了检测实体是新的还是预期存在于数据库中的策略。
一个重要的约束是,保存实体后,实体不得再是新的。请注意,实体是否是新的,是实体状态的一部分。对于自增列,这会自动发生,因为 ID 会由 Spring Data 使用 ID 列中的值进行设置。
模板 API
作为存储库的替代方案,Spring Data JDBC 提供 JdbcAggregateTemplate,作为在关系数据库中加载和持久化实体的更直接方式。在很大程度上,存储库使用 JdbcAggregateTemplate 来实现其功能。
本节仅强调 JdbcAggregateTemplate 最有趣的部分。有关更完整的概述,请参阅 JdbcAggregateTemplate 的 JavaDoc。
访问 JdbcAggregateTemplate
JdbcAggregateTemplate 旨在用作 Spring bean。如果您已将应用程序设置为包含 Spring Data JDBC,则可以在任何 Spring bean 中配置对 JdbcAggregateTemplate 的依赖关系,Spring Framework 将注入一个正确配置的实例。
这包括您用于为 Spring Data Repository 实现自定义方法的代码片段,允许您使用 JdbcAggregateTemplate 自定义和扩展您的 Repository。
持久化
JdbcAggregateTemplate 提供三种类型的持久化实体方法:save、insert 和 update。每种方法都有两种形式:对单个聚合进行操作,名称与上述完全相同;以及带有 All 后缀,对 Iterable 进行操作。
save 与存储库中同名方法的功能相同。
insert 和 update 跳过实体是否为新的检测,并分别假定新的或已存在的聚合,正如它们的名称所示。
查询
JdbcAggregateTemplate 提供了大量的查询聚合和聚合集合的方法。其中有一种方法需要特别注意。那就是接受 Query 作为参数的方法。它们允许执行程序化构造的查询,如下所示
template.findOne(query(where("name").is("Gandalf")), Person.class);
query 方法返回的 Query 定义了要选择的列列表、where 子句(通过 CriteriaDefinition),以及 limit 和 offset 子句的规范。有关 Query 类的详细信息,请参阅其 JavaDoc。
Criteria 类(其中 where 是一个静态成员)提供了 org.springframework.data.relational.core.query.CriteriaDefinition[] 的实现,它们代表了查询的 where 子句。
Criteria 类的方法
Criteria 类提供以下方法,所有这些方法都对应于 SQL 运算符
-
Criteriaand(String column):将一个带有指定property的链式Criteria添加到当前Criteria并返回新创建的那个。 -
Criteriaor(String column):将一个带有指定property的链式Criteria添加到当前Criteria并返回新创建的那个。 -
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运算符通过集合创建条件。
乐观锁定
Spring Data 通过聚合根上用 @Version 注解的数字属性支持乐观锁定。每当 Spring Data 保存带有此版本属性的聚合时,会发生两件事:
-
聚合根的更新语句将包含一个 where 子句,检查数据库中存储的版本是否确实未更改。
-
如果不是这种情况,将抛出
OptimisticLockingFailureException。
此外,版本属性在实体和数据库中都会增加,因此并发操作会注意到更改,并在适用时(如上所述)抛出 OptimisticLockingFailureException。
这个过程也适用于插入新的聚合,其中 null 或 0 版本表示一个新实例,随后的增加实例将实例标记为不再是新的,这使得它在对象构建期间生成 ID 的情况下(例如使用 UUID 时)工作得非常好。
在删除期间,版本检查也适用,但版本不会增加。