持久化实体

可以使用 CrudRepository.save(…) 方法保存聚合。如果聚合是新的,则会导致插入聚合根,然后插入所有直接或间接引用的实体的插入语句。

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

这种方法有一些明显的缺点。如果只有少数引用的实体实际上发生了更改,那么删除和插入就是浪费。虽然这个过程可以而且可能会得到改进,但 Spring Data JDBC 所能提供的功能有一定的局限性。它不知道聚合的先前状态。因此,任何更新过程都必须始终获取数据库中找到的任何内容,并确保将其转换为传递给 save 方法的实体的状态。

另请参阅 实体状态检测 以了解更多详细信息。

加载聚合

Spring Data JDBC 提供了两种加载聚合的方法

  1. 传统版本和 3.2 之前的版本中,唯一的方法非常简单:每个查询都会加载聚合根,而不管查询是基于 CrudRepository 方法、派生查询还是带注释的查询。如果聚合根引用其他实体,则会使用单独的语句加载这些实体。

  2. Spring Data JDBC 3.2 允许使用单查询加载。使用此方法,可以使用单个 SQL 查询完全加载任意数量的聚合。这应该会显著提高效率,特别是对于由许多实体组成的复杂聚合。

    目前,单查询加载受到不同方式的限制

    1. 聚合不得有嵌套集合,其中包括 Map。计划在未来移除此限制。

    2. 聚合不得使用 AggregateReference 或嵌入式实体。计划在未来移除此限制。

    3. 数据库方言必须支持它。Spring Data JDBC 提供的所有方言中,除了 H2 和 HSQL 外,其他都支持此功能。H2 和 HSQL 不支持分析函数(又称窗口函数)。

    4. 它仅适用于 CrudRepository 中的 find 方法,不适用于派生查询和带注释的查询。计划在未来移除此限制。

    5. 需要在 JdbcMappingContext 中启用单查询加载,方法是调用 setSingleQueryLoadingEnabled(true)

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

单查询加载应被视为实验性的。我们欢迎您对它如何为您工作提供反馈。
虽然单查询加载可以缩写为 SQL,但我们强烈不建议这样做,因为几乎肯定会与结构化查询语言混淆。

ID 生成

Spring Data 使用标识符属性来识别实体。实体的 ID 必须使用 Spring Data 的 @Id 注释进行注释。

当您的数据库为 ID 列设置了自动增量列时,在将实体插入数据库后,会在实体中设置生成的值。

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

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

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

乐观锁

Spring Data 通过一个使用 @Version 在聚合根上进行注释的数字属性来支持乐观锁。每当 Spring Data 保存具有此类版本属性的聚合时,都会发生两件事

  • 聚合根的更新语句将包含一个 where 子句,检查数据库中存储的版本实际上未更改。

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

此外,版本属性在实体和数据库中都会增加,因此并发操作会注意到更改,并在适用时抛出 OptimisticLockingFailureException,如上所述。

此过程也适用于插入新聚合,其中 null0 版本表示新实例,并且增加的实例随后将实例标记为不再是新实例,从而在例如使用 UUID 时在对象构造期间生成 ID 的情况下很好地工作。

在删除期间,版本检查也适用,但不会增加版本。