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 上下文中的關聯性。相信我,這很重要。

本文基於我在 Spring One 2021 上所做的演講的一部分。

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 中。

導覽這樣的參考會轉化為使用目標聚合的 repository 及其 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 中宣告一個方法,該方法為給定的邪惡主人尋找適當的 minions。

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 repository 中找到。這裡有一個內部參考的範例一個外部參考的範例

未來還會有更多類似的文章。如果您希望我涵蓋特定主題,請告訴我。

取得 Spring 電子報

隨時掌握 Spring 電子報的最新資訊

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

瞭解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

瞭解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看所有