这是关于如何解决你在使用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 ,它应该有一个对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注解,而不是JPA注解来使这些回溯引用@Transient 。否则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示例库中找到。 有一个内部引用的示例和一个外部引用的示例。
以后还会有更多类似的文章,如果你想让我介绍特定的主题,请告诉我。