先行一步
VMware 提供培训和认证,助您快速提升。
了解更多这是关于如何解决使用 Spring Data JDBC 时可能遇到的各种挑战的系列文章的第二篇。本系列包括:
Spring Data JDBC - 如何创建双向关系?(本文)
如果您是 Spring Data JDBC 的新手,应该先阅读其 介绍 和 这篇解释聚合在 Spring Data JDBC 上下文中重要性的文章。相信我,这很重要。
本文基于我在 2021 年 Spring One 大会上的一个演讲 的部分内容。
Spring Data JDBC 没有对双向关系提供特殊支持。为了理解为什么您实际上不需要任何特殊支持,我们必须看看两种不同类型的关系:我们区分聚合内部的引用和跨聚合的引用。
让我们首先看看聚合内部的引用。这些在 Spring Data JDBC 中通过实际的 Java 引用来建模。这些引用总是从聚合根指向聚合内部的实体。实际上,引用是从更靠近聚合根的实体指向更深层次的实体。但是相同的论点也适用,所以我们只考虑聚合根和一个内部实体。
如果您遵循 DDD 的思想和规则,您永远不会直接访问内部实体。相反,每当您想操作内部实体时,您都会调用聚合根上的一个方法,然后聚合根会调用内部实体上适当的方法。如果该方法需要聚合根的引用,您只需在调用内部实体上的方法时将其传递过去即可。中间实体也是如此。
但也许您有很多这样的方法,并且不想到处传递 this
。在这种情况下,您只需在聚合构造时而不是在方法调用时传递引用即可。这只是普通的 Java 代码,没有什么特别之处。
举个例子,考虑一个 Minion
和它的 Toy
,Toy
应该有一个指向 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 仓库中找到。这里有一个内部引用的示例,这里有一个外部引用的示例。
将来会有更多类似的文章。如果您想让我涵盖特定主题,请告诉我。