持久化实体
保存聚合可以通过 CrudRepository.save(…)
方法执行。如果聚合是新的,这将导致对聚合根进行插入操作,随后对所有直接或间接引用的实体执行插入语句。
如果聚合根不是新的,所有引用的实体将被删除,聚合根将被更新,并且所有引用的实体将再次插入。请注意,实例是否为新的状态是实例状态的一部分。
这种方法有一些明显的缺点。如果只有少数被引用的实体实际发生了变化,那么删除和插入操作是浪费的。尽管这个过程可能会并且很可能会得到改进,但 Spring Data JDBC 所能提供的功能存在一定的局限性。它不知道聚合的先前状态。因此,任何更新过程都必须处理它在数据库中找到的内容,并确保将其转换为传递给保存方法的实体的状态。
另请参阅实体状态检测以获取更多详细信息。
加载聚合数据
Spring Data JDBC 提供了两种加载聚合的方式:
-
传统的方式以及在 3.2 版本之前唯一的方式非常简单:每个查询都会加载聚合根,无论查询是基于
CrudRepository
方法、派生查询还是注解查询。如果聚合根引用了其他实体,这些实体将通过单独的语句加载。 -
Spring Data JDBC 3.2 允许使用单查询加载。通过这种方式,可以使用单个 SQL 查询完全加载任意数量的聚合。这对于由许多实体组成的复杂聚合来说,应该会显著提高效率。
目前,单查询加载在以下几个方面受到限制:
-
聚合不能有嵌套集合,包括
Map
。计划在未来移除这一限制。 -
聚合不能使用
AggregateReference
或嵌入实体。计划在未来移除这一限制。 -
数据库方言必须支持该功能。在 Spring Data JDBC 提供的方言中,除了 H2 和 HSQL 外,其他都支持此功能。H2 和 HSQL 不支持分析函数(即窗口函数)。
-
该功能仅适用于
CrudRepository
中的查找方法,不适用于派生查询和注解查询。计划在未来移除这一限制。 -
单查询加载需要在
JdbcMappingContext
中通过调用setSingleQueryLoadingEnabled(true)
来启用。
-
如果任何条件不满足,Spring Data JDBC 将回退到默认的加载聚合的方式。
单查询加载功能目前处于实验阶段。我们非常欢迎您提供关于其使用效果的反馈。
虽然单查询加载(Single Query Loading)可以缩写为 SQL,但我们强烈建议不要这样做,因为这几乎肯定会与结构化查询语言(Structured Query Language)混淆。
ID 生成
Spring Data 使用标识符属性来识别实体。实体的 ID 必须使用 Spring Data 的 @Id 注解进行标注。
当你的数据库中有一个用于 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 框架将注入一个正确配置的实例。
这包括用于为 Spring Data 仓库实现自定义方法的片段,让你能够使用 JdbcAggregateTemplate
来自定义和扩展你的仓库。
持久化
JdbcAggregateTemplate
提供了三种类型的持久化实体的方法:save
、insert
和 update
。每种方法都有两种形式:一种是操作单个聚合,方法名与上述完全相同;另一种是在 Iterable
上带有 All
后缀的操作。
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
and(String column)
: 将具有指定property
的链式Criteria
添加到当前Criteria
中,并返回新创建的Criteria
。 -
Criteria
or(String column)
: 将具有指定property
的链式Criteria
添加到当前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 保存一个带有此类版本属性的聚合时,会发生以下两件事:
-
聚合根的更新语句将包含一个 where 子句,用于检查数据库中存储的版本是否确实未更改。
-
如果不是这种情况,将抛出
OptimisticLockingFailureException
。
此外,实体和数据库中的 version
属性都会增加,因此并发操作会注意到这一变化,并在适用的情况下抛出 OptimisticLockingFailureException
,如上所述。
此过程同样适用于插入新的聚合体,其中 null
或 0
版本表示一个新实例,随后的递增版本将该实例标记为不再是新实例。这在与对象构造期间生成 id 的情况下(例如使用 UUID 时)配合得非常好。
在删除过程中也会进行版本检查,但不会增加版本号。