Spring Data JDBC如何使用自定义 ID 生成

这是关于如何解决在使用Spring数据JDBC时可能遇到的各种挑战的系列文章的第一篇文章。该系列包括

如果您不熟悉春季数据JDBC,则应首先阅读其介绍本文解释了聚合在春季数据 JDBC 上下文中的相关性.相信我,这很重要。

现在我们可以开始使用ID - 特别是当您想要控制实体的ID而不想将其留给数据库时,您的选项是什么。但是,让我们首先重申春季数据JDBC对此的默认策略。

默认情况下,弹簧数据 JDBC 假定 ID 由某种类型的SERIALIDENTITY或​​​​​​​AUTOINCREMENT列生成。它基本上检查聚合根的 ID 是基元数类型​​​​​​​null还是基元数类型0。如果是​​​​​​​​​​​​​​null,则假定聚合是新的,并为聚合根执行插入。数据库生成一个 ID,该 ID 由弹簧数据 JDBC 在聚合根中设置。如果 ID 不是null,则假定聚合是现有 ID,并为聚合根执行更新。

考虑一个由单个简单类组成的简单聚合:

class Minion {
	@Id
	Long id;
	String name;

	Minion(String name) {
		this.name = name;
	}
}

进一步考虑默认CrudRepository

interface MinionRepository extends CrudRepository<Minion, Long> {

}

存储库通过如下所示的行自动连接到您的代码中:

	@Autowired
	MinionRepository minions;

以下工作正常:

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 数据 JDBC 会尝试执行更新,因为 ID 已设置。但是,由于聚合实际上是新的,因此更新语句会影响零行,并且 Spring 数据 JDBC 会引发异常。

有几种方法可以解决这个问题。为此,我发现了四种不同的方法,我首先列出了我认为最简单的方法,因此一旦找到适合您的解决方案,您就可以停止阅读。您可以稍后再回来阅读其他选项并提高您的Spring数据技能。

版本

将版本属性添加到聚合属性。通过“版本属性”,我的意思是用@Version注释的属性。此类属性的主要目的是启用乐观锁定。但是,作为副作用,版本属性也会被 Spring 数据 JDBC 用于确定聚合根是否是新的。只要版本是​​​​​​​null或​​​​​​​0对于基元类型,聚合就被视为新聚合,即使设置了​​​​​​​id

使用这种方法,您必须更改实体和(当然)架构,但仅此而已。

此外,对于许多应用程序,乐观锁定首先是一件好事。

我们将原件Minion变成VersionedMinion:​​​​​​​

class VersionedMinion {

	@Id Long id;
	String name;
	@Version Integer version;

	VersionedMinion(long id, String name) {

		this.id = id;
		this.name = name;
	}
}

存储库和自动布线看起来与原始示例基本相同。进行此更改后,以下构造将起作用:

VersionedMinion before = new VersionedMinion(23L, "Bob");

assertThat(before.id).isNotNull();

versionedMinions.save(before);

VersionedMinion reloaded = versionedMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Bob");

模板

使用ID获得遗嘱的另一种方法是自己进行插入。您可以通过注入JdbcAggregateTemplate并调用JdbcAggregateTemplate.insert(T)来执行此操作。JdbcAggregateTemplate是存储库下方的抽象层,因此您使用的代码与存储库用于插入的代码相同,但您可以决定何时使用插入:​​​​​​​

Minion before = new Minion("Stuart");
before.id = 42L;

template.insert(before);

Minion reloaded = minions.findById(42L).get();
assertThat(reloaded.name).isEqualTo("Stuart");

请注意,我们不使用存储库,而是使用模板,该模板注入了以下内容:

@Autowired
JdbcAggregateTemplate template;

事件接收器

模板方法非常适合您已经知道 ID 的情况 - 例如,当您从另一个系统导入数据并希望重用该系统的 ID 时。

如果您不知道 ID,并且不希望在业务代码中包含任何 ID,则使用回调可能是更好的选择。

回调是在某些生命周期事件中被调用的 Bean。对于我们的目的来说,正确的回调是BeforeConvertCallback .它返回可能修改的聚合根,因此也适用于不可变的实体类。

在回调中,我们确定有问题的聚合根是否需要新的 ID。如果是这样,我们通过使用我们选择的算法来生成它。

我们使用的另一种变体Minion

class StringIdMinion {
	@Id
	String id;
	String name;

	StringIdMinion(String name) {
		this.name = name;
	}
}

存储库和注入点看起来仍然类似于原始示例。但是,我们在配置中注册回调:

@Bean
BeforeConvertCallback<StringIdMinion> beforeConvertCallback() {

	return (minion) -> {
		if (minion.id == null) {
			minion.id = UUID.randomUUID().toString();
		}
		return minion;
	};
}

用于保存实体的代码现在看起来就像是由数据库生成的id一样:

StringIdMinion before = new StringIdMinion("Kevin");

stringions.save(before);

assertThat(before.id).isNotNull();

StringIdMinion reloaded = stringions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Kevin");

持久性

最后一个选项是让聚合根控制是否应进行更新或插入。您可以通过实现接口Persistable(尤其是方法isNew)来执行此操作。最简单的方法是一直返回​​​​​​​​​​​​​​true,从而一直强制插入。当然,当您也想使用聚合根进行更新时,这不起作用。在这种情况下,您需要提出更灵活的策略。

我们需要再次调整我们的Minion

class PersistableMinion implements Persistable<Long> {
	@Id Long id;
	String name;

	PersistableMinion(Long id, String name) {
		this.id = id;
		this.name = name;
	}

	@Override
	public Long getId() {
		return id;
	}

	@Override
	public boolean isNew() {
		// this implementation is most certainly not suitable for production use
		return true;
	}
}

用于保存PersistableMinion的代码看起来完全相同:

PersistableMinion before = new PersistableMinion(23L, "Dave");

persistableMinions.save(before);

PersistableMinion reloaded = persistableMinions.findById(before.id).get();
assertThat(reloaded.name).isEqualTo("Dave");

结论

春季数据 JDBC 为如何控制聚合的 ID 提供了大量选项。虽然我在示例中使用了琐碎的逻辑,但没有什么能阻止您实现您想到的任何逻辑,因为它们都归结为非常基本的Java代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值