持久化实体
保存一个聚合可以使用 CrudRepository.save(…) 方法。
如果该聚合是新的,则会首先对聚合根执行插入操作,然后对其所有直接或间接引用的实体依次执行插入语句。
如果聚合根不是新的,则所有引用的实体都会被删除,聚合根会被更新,并且所有引用的实体会再次被插入。 请注意,一个实例是否为新实例属于该实例状态的一部分。
| 这种方法存在一些明显的缺点。 如果被引用的实体中只有少数几个实际上发生了变更,那么删除和重新插入就是一种浪费。 尽管这一过程可以而且很可能会得到改进,但 Spring Data JDBC 的能力仍存在一定限制。 它无法获知聚合的先前状态。 因此,任何更新操作都必须以数据库中当前存在的数据为基础,并确保将其转换为传递给 save 方法的实体所表示的状态。 |
另请参阅实体状态检测以获取更多详细信息。
正在加载聚合对象
Spring Data JDBC 提供了两种加载聚合的方式:
-
传统方式(在 3.2 版本之前也是唯一的方式)非常简单: 每个查询都会加载聚合根,无论该查询是基于
CrudRepository方法、派生查询还是带注解的查询。 如果聚合根引用了其他实体,则这些实体会通过单独的语句进行加载。 -
Spring Data JDBC 3.2 支持使用单查询加载(Single Query Loading)。 通过这种方式,可以使用一条 SQL 查询完整加载任意数量的聚合根。 这应该会显著提升效率,特别是对于由多个实体组成的复杂聚合根。
目前,单查询加载(Single Query Loading)在不同方面受到限制:
-
聚合对象不得包含嵌套的集合,这包括
Map。 计划在未来移除这一限制。 -
聚合不得使用
AggregateReference或嵌入式实体。 计划在未来移除这一限制。 -
数据库方言必须支持该功能。在 Spring Data JDBC 提供的所有方言中,除了 H2 和 HSQL 之外,其余都支持此功能。 H2 和 HSQL 不支持分析函数(也称为窗口函数)。
-
它仅适用于
CrudRepository中的 find 方法,不适用于派生查询,也不适用于带注解的查询。 计划在未来移除这一限制。 -
必须通过调用
JdbcMappingContext在setSingleQueryLoadingEnabled(true)中启用单查询加载(Single Query Loading)。
-
如果任何条件未满足,Spring Data JDBC 将回退到加载聚合的默认方法。
| 单查询加载(Single Query Loading)目前被视为实验性功能。 我们非常欢迎您提供关于其实际使用效果的反馈。 |
| 虽然单查询加载(Single Query Loading)可以缩写为 SQL,但我们强烈不建议这样做,因为几乎肯定会与结构化查询语言(Structured Query Language)产生混淆。 |
ID 生成
Spring Data 使用标识符属性来识别实体。
也就是说,查找这些实体或创建针对特定行的语句。
实体的 ID 必须使用 Spring Data 的 @Id 注解进行标注。
当您的数据库的 ID 列是自增列时,在将实体插入数据库后,生成的值会被设置到该实体中。
如果你在标识符属性上额外添加 @Sequence 注解,并且底层的 Dialect 支持序列(sequences),则将使用数据库序列来获取 ID 的值。
否则,当实体为新实体且标识符值为其初始默认值时,Spring Data 不会尝试向标识符列插入值。
对于基本数据类型,该初始值为 0;如果标识符属性使用了如 null 这样的数值包装类型,则初始值为 Long。
实体状态检测详细解释了用于判断一个实体是新实体,还是预期已在数据库中存在的策略。
一个重要的约束是,在保存实体之后,该实体不能再处于“新建”状态。 请注意,实体是否为新建状态是实体自身状态的一部分。 对于自增列,这一过程会自动完成,因为 Spring Data 会使用 ID 列的值来设置实体的 ID。
模板 API
作为存储库的替代方案,Spring Data JDBC 提供了 JdbcAggregateTemplate,作为一种更直接的方式在关系型数据库中加载和持久化实体。
在很大程度上,存储库使用 JdbcAggregateTemplate 来实现其功能。
本节仅重点介绍 JdbcAggregateTemplate 中最有趣的部分。
如需更全面的概述,请参阅 JdbcAggregateTemplate 的 JavaDoc。
访问 JdbcAggregateTemplate
JdbcAggregateTemplate 旨在作为 Spring Bean 使用。
如果您已将应用程序配置为包含 Spring Data JDBC,则可以在任意 Spring Bean 中声明对 JdbcAggregateTemplate 的依赖,Spring 框架将注入一个正确配置的实例。
这包括您用于为 Spring Data 仓库实现自定义方法的片段,使您可以使用 JdbcAggregateTemplate 来定制和扩展您的仓库。
持久化
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 操作符:
-
Criteria和(String column):向当前Criteria添加一个带有指定property的链式Criteria,并返回新创建的5。 -
Criteria或(String column):向当前Criteria添加一个带有指定property的链式Criteria,并返回新创建的5。 -
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异常。
此外,version 属性在实体和数据库中都会递增,因此并发操作将察觉到这一变更,并在适用的情况下抛出 OptimisticLockingFailureException,如上文所述。
该过程同样适用于插入新的聚合对象,其中 null 或 0 的版本号表示一个新实例,而随后递增的版本号则将该实例标记为不再是新的。这种方式在例如使用 UUID 作为 ID 并在对象构造期间生成 ID 的场景下,能够很好地发挥作用。
在执行删除操作时,版本检查同样适用,但版本号不会增加。