原标题:Spring认证|Spring Data JDBC-如何使用自定义ID生成
这是关于如何解决使用 Spring Data JDBC 时可能遇到的各种挑战的系列文章的第一篇。
如果你不了解 Spring Data JDBC,你应该首先阅读它的介绍和文章,它解释了 Spring Data JDBC 上下文中的相关性。相信我,这很重要。
文章基于我在 2021 年春季一期上这篇文章的部分演讲。
使用 ID - 特别是当您想要控制实体的 ID 并且不会选择什么数据库时,您的选择是什么。
假设情况下,类型数据列JDBC假设的ID通过生成SERIAL或AUTOINCREMENT得到。 ,聚合根执行插入操作。数据库生成一个ID,这个ID由Spring Data JDBC在聚合根中设置。
考虑一个由单个简单的类组成的简单聚合:
类小黄人{
@ID
长ID;
字符串名称;
Minion(字符串名称){
this.name = 名称;
}
}
进一步考虑默认CrudRepository。
接口 MinionRepository 扩展 CrudRepository {
}
存储库会自动连接到您的代码中,如下所示:
@自动连线
MinionRepository 随从;
以下工作正常:
Minion before = new Minion("Bob");
assertThat(before.id).isNull();
Minion after = minions.save(before);
assertThat(after.id).isNotNull();
但是下一点点:
Minion before = new Minion("Stuart");
before.id = 42L;
minions.save(before);
更新语句,Spring Data JDBC 尝试执行更新,因为 ID 已经设置。但是,因为实际上是新的,更新语句影响零行 Spring Data JDBC 抛出异常。
有几种方法可以解决这个问题。我已经找到了你不同的解决方法,并且已经找到了我认为最简单的方法,因此可以找到适合的方法,你就可以停止阅读。之后回来阅读其他选项并提高您的 Spring Data 技能。
版本
将版本属性添加到您的聚合属性。“版本属性”是指用@Version。此类的主要目的是可以乐观锁定。但是,作为属性,Spring Data JDBC 使用版本属性来确定聚合根是否是新的。 只要版本是null 或0 原始类型,聚合就被认为是新的,即使id设置了。
使用这种方法,您必须更改实体和(当然)系统,但别无其他。
此外,对于许多应用程序来说,乐观的最初是很多。
我们把原来的Minion变成了一个VersionedMinion:
类 VersionedMinion {
@Id 长 ID;
字符串名称;
@Version 整数版本;
VersionedMinion(长ID,字符串名称){
this.id = id;
this.name = 名称;
}
}
通过此更改,以下构造有效:
VersionedMinion before = new VersionedMinion(23L, "Bob");
assertThat(before.id).isNotNull();
versionedMinions.save(before);
VersionedMinion 重新加载 = versionedMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Bob");
样板
一种让您的遗赠附带 ID 的方法是自己另外插入物。您可以通过注入 JdbcAggregateTemplate 并调用 JdbcAggregateTemplate.insert(T)。这JdbAggregateTemplate是存储库下面的底层,因此您使用存储库用于插入的相同代码,但您决定何时使用插入:
Minion before = new Minion("Stuart");
before.id = 42L;
模板.插入(之前);
Minion reloaded = minions.findById(42L).get();
assertThat(reloaded.name).isEqualTo("Stuart");
请注意,我们不使用存储库农场使用模板,其中注入了以下内容:
@自动连线
JdbcAggregateTemplate 模板;
事件监听器
模板方法非常适用于您已经知道 ID 的情况 - 例如,当您从另一个系统导入数据并且您想要重用该系统的 ID 时。
如果您不知道 ID 并且不想在您的业务代码中包含任何 ID 相关的内容,那么使用 ID 可能是更好的选择。
我们的目的正确的目的是在某些生命周期事件期间被调用的豆子。它返回修改潜在的聚合根,因此它也适用于不形成实体类。
在目标中,我们确定有问题的聚合根是否需要新 ID。 如果是这样,我们将使用我们选择的算法生成它。
我们使用另一种变体 Minion
类 StringIdMinion {
@ID
字符串标识;
字符串名称;
StringIdMinion(字符串名称){
this.name = 名称;
}
}
但是,我们在配置中注册了一个惊人的例子:
@豆角,扁豆
BeforeSaveCallback beforeSaveCallback() {
返回(minion,mutableAggregateChange)-> {
如果(minion.id == null){
minion.id = UUID.randomUUID().toString();
}
返回仆从;
};
}
保存实体的代码现在看起来就像是由数据库生成的:
StringIdMinion before = new StringIdMinion("Kevin");
stringions.save(before);
assertThat(before.id).isNotNull();
StringIdMinion reloaded = stringions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Kevin");
持久的
一个选项是让化根控制是否应该更新或插入。你可以实现持久化的方法(尤其是实现是新的)来实现这一点。您也想使用聚合根进行更新时,这会抓住。在这种情况下,您需要提出更灵活的策略。
我们需要 Minion 再次调整我们的:
类 PersistableMinion 实现 Persistable {
@Id 长 ID;
字符串名称;
PersistableMinion(长ID,字符串名称){
this.id = id;
this.name = 名称;
}
@覆盖
公共长 getId() {
返回标识;
}
@覆盖
公共布尔 isNew() {
// 这个实现肯定不适合生产使用
返回真;
}
}
保存一个的代码 PersistableMinion 看起来是一样的:
PersistableMinion before = new PersistableMinion(23L, "Dave");
persistableMinions.save(before);
PersistableMinion 重新加载 = persistableMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Dave");
结论
Spring Data JDBC 提供了大量关于如何控制聚合 ID 的选项。虽然我在示例中使用了非常严重的逻辑,但基本没有什么能阻止您实现您所考虑的任何逻辑,因为它们都归结为 Java 代码。
完整的示例代码可在Spring中国教育管理中心(Spring认证)数据示例库访问!