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 社群的所有近期活動。

檢視全部