Spring Data JDBC - 如何创建双向关系?

工程 | Jens Schauder | 2021 年 9 月 22 日 | ...

这是关于如何解决使用 Spring Data JDBC 时可能遇到的各种挑战的系列文章的第二篇。本系列包括:

  1. Spring Data JDBC - 如何使用自定义 ID 生成。

  2. Spring Data JDBC - 如何创建双向关系?(本文)

  3. Spring Data JDBC - 如何实现缓存?

  4. Spring Data JDBC - 如何对聚合根进行部分更新?

  5. Spring Data JDBC - 如何为我的领域模型生成 Schema?

如果您是 Spring Data JDBC 的新手,应该先阅读其 介绍这篇解释聚合在 Spring Data JDBC 上下文中重要性的文章。相信我,这很重要。

本文基于我在 2021 年 Spring One 大会上的一个演讲 的部分内容。

Spring Data JDBC 没有对双向关系提供特殊支持。为了理解为什么您实际上不需要任何特殊支持,我们必须看看两种不同类型的关系:我们区分聚合内部的引用和跨聚合的引用。

内部引用

让我们首先看看聚合内部的引用。这些在 Spring Data JDBC 中通过实际的 Java 引用来建模。这些引用总是从聚合根指向聚合内部的实体。实际上,引用是从更靠近聚合根的实体指向更深层次的实体。但是相同的论点也适用,所以我们只考虑聚合根和一个内部实体。

如果您遵循 DDD 的思想和规则,您永远不会直接访问内部实体。相反,每当您想操作内部实体时,您都会调用聚合根上的一个方法,然后聚合根会调用内部实体上适当的方法。如果该方法需要聚合根的引用,您只需在调用内部实体上的方法时将其传递过去即可。中间实体也是如此。

但也许您有很多这样的方法,并且不想到处传递 this。在这种情况下,您只需在聚合构造时而不是在方法调用时传递引用即可。这只是普通的 Java 代码,没有什么特别之处。

举个例子,考虑一个 Minion 和它的 ToyToy 应该有一个指向 Minion 的引用,以便它能说出主人的名字。Minion 将自己设置为所有玩具的主人。

class Minion {
	@Id
	Long id;
	String name;
	final Set<Toy> toys = new HashSet<>();

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

	@PersistenceConstructor
	private Minion(Long id, String name, Collection<Toy> toys) {

		this.id = id;
		this.name = name;
		toys.forEach(this::addToy);
	}

	public void addToy(Toy toy) {
		toys.add(toy);
		toy.minion = this;
	}

	public void showYourToys() {
		toys.forEach(Toy::sayHello);
	}
}

class Toy {
	String name;

	@Transient // org.SPRINGframework.DATA...
	Minion minion;

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

	public void sayHello() {
		System.out.println("I'm " + name + " and I'm a toy of " + minion.name);
	}
}

请注意,您需要使用 Spring Data 的 @Transient 注解(而不是 JPA 的)来标记这些反向引用。否则 Spring Data JDBC 会尝试持久化它们,这将导致无限循环。

外部引用

聚合之间的引用情况甚至更简单。这些引用不是通过 Java 引用实现的,而是通过使用被引用聚合的 ID 来实现,可以选择将其包装在 AggregateReference 中。

导航此类引用意味着使用目标聚合的仓库及其 findById 方法。例如,一个 Minion 可能引用其邪恶的主人,一个 Person

class Minion {
	@Id
	Long id;
	String name;
	AggregateReference<Person, Long> evilMaster;

	Minion(String name, AggregateReference<Person, Long> evilMaster) {
		this.name = name;
		this.evilMaster = evilMaster;
	}
}

class Person {
	@Id
	Long id;
	String name;

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

给定一个 Minion,您现在可以加载其邪恶的主人。

@Autowired
PersonRepository persons;

//...

Minion minion = //...

Optional<Person> evilMaster = persons.findById(minion.evilMaster.getId());

为了反向导航关系,您可以在 MinionRepository 中声明一个方法,该方法用于查找给定邪恶主人的相应随从。

interface MinionRepository extends CrudRepository<Minion, Long> {

	@Query("SELECT * FROM MINION WHERE EVIL_MASTER = :id")
	Collection<Minion> findByEvilMaster(Long id);
}

@Autowired
MinionRepository minions;

//...

Person evilMaster = // ...

Collection<Minion>findByEvilMaster(evilMaster.id);

使用 Spring Data JDBC 2.3,您不再需要使用 @Query 注解,因为查询派生支持将 AggregateReference 作为参数类型。

结论

虽然 Spring Data JDBC 没有对双向关系提供明确的支持,但事实证明您不需要特殊支持。您所需要的只是现有功能和标准 Java 代码。完整的示例代码可在 Spring Data Example 仓库中找到。这里有一个内部引用的示例这里有一个外部引用的示例

将来会有更多类似的文章。如果您想让我涵盖特定主题,请告诉我。

获取 Spring 电子报

订阅 Spring 电子报,保持联系

订阅

先行一步

VMware 提供培训和认证,助您快速提升。

了解更多

获取支持

Tanzu Spring 通过一种简单的订阅方式,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部