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註解(而不是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中宣告一個方法,該方法為給定的邪惡主人查詢適當的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示例倉庫中找到。這裡有一個內部引用的例子還有一個外部引用的例子

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

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有