唯一ID的處理與提供

使用內部 Neo4j ID

為您的領域類提供唯一識別符號的最簡單方法是在型別為 StringLong(物件型別,而非標量 long 更好,因為字面量 null 是判斷例項是否為新的更好指標)的欄位上結合使用 @Id@GeneratedValue

示例 1. 具有內部 Neo4j ID 的可變 MovieEntity
@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue
	private Long id;

	private String name;

	public MovieEntity(String name) {
		this.name = name;
	}
}

您無需為該欄位提供 setter,SDN 會使用反射來賦值,但如果存在 setter 則會使用。如果您想建立一個具有內部生成 ID 的不可變實體,則必須提供一個 wither

示例 2. 具有內部 Neo4j ID 的不可變 MovieEntity
@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue
	private final Long id; (1)

	private String name;

	public MovieEntity(String name) { (2)
		this(null, name);
	}

	private MovieEntity(Long id, String name) { (3)
		this.id = id;
		this.name = name;
	}

	public MovieEntity withId(Long id) { (4)
		if (this.id.equals(id)) {
			return this;
		} else {
			return new MovieEntity(id, this.title);
		}
	}
}
1 表示生成值的不可變 final id 欄位
2 公共建構函式,供應用程式和 Spring Data 使用
3 內部使用的建構函式
4 這是一個針對 id 屬性的所謂 wither。它建立一個新實體並相應地設定欄位,而不修改原始實體,從而使其不可變。

您必須為 id 屬性提供一個 setter,或者提供像 wither 這樣的東西,如果您想擁有

  • 優點:id 屬性是代理業務鍵,這非常明確,使用它不需要額外的努力或配置。

  • 缺點:它繫結到 Neo4j 的內部資料庫 ID,這在資料庫的整個生命週期內對我們的應用程式實體來說並非唯一。

  • 缺點:建立不可變實體需要更多努力

使用外部提供的代理鍵

@GeneratedValue 註解可以接受實現 org.springframework.data.neo4j.core.schema.IdGenerator 介面的類作為引數。SDN 開箱即用地提供了 InternalIdGenerator(預設)和 UUIDStringGenerator。後者為每個實體生成新的 UUID,並將其作為 java.lang.String 返回。使用它的應用程式實體如下所示

示例 3. 具有外部生成代理鍵的可變 MovieEntity
@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue(UUIDStringGenerator.class)
	private String id;

	private String name;
}

關於優點和缺點,我們需要討論兩件事:賦值本身和 UUID 策略。一個通用唯一識別符號 (universally unique identifier) 旨在用於實際目的的唯一性。引用維基百科:“因此,任何人都可以建立一個 UUID 並用它來標識某物,幾乎可以確定該識別符號不會與已建立或將要建立用於標識其他事物的識別符號重複。” 我們的策略使用 Java 內部的 UUID 機制,採用密碼學上強大的偽隨機數生成器。在大多數情況下,這應該能正常工作,但具體效果可能因情況而異。

接下來討論賦值本身

  • 優點:應用程式完全掌控生成過程,可以生成一個對於應用程式目的來說足夠唯一的鍵。生成的值將是穩定的,之後無需更改。

  • 缺點:生成策略是在應用程式端實現的。如今,大多數應用程式會部署在多個例項上以實現良好的伸縮性。如果您的策略容易生成重複項,那麼插入操作將會失敗,因為主鍵的唯一性屬性將被違反。因此,在這種情況下,雖然您不必考慮唯一的業務鍵,但必須更多地考慮要生成什麼。

您有幾種方式來實現自己的 ID 生成器。一種是實現生成器的 POJO

示例 4. 簡單的序列生成器
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.data.neo4j.core.schema.IdGenerator;
import org.springframework.util.StringUtils;

public class TestSequenceGenerator implements IdGenerator<String> {

	private final AtomicInteger sequence = new AtomicInteger(0);

	@Override
	public String generateId(String primaryLabel, Object entity) {
		return StringUtils.uncapitalize(primaryLabel) +
			"-" + sequence.incrementAndGet();
	}
}

另一種選擇是提供一個額外的 Spring Bean,如下所示

示例 5. 基於 Neo4jClient 的 ID 生成器
@Component
class MyIdGenerator implements IdGenerator<String> {

	private final Neo4jClient neo4jClient;

	public MyIdGenerator(Neo4jClient neo4jClient) {
		this.neo4jClient = neo4jClient;
	}

	@Override
	public String generateId(String primaryLabel, Object entity) {
		return neo4jClient.query("YOUR CYPHER QUERY FOR THE NEXT ID") (1)
			.fetchAs(String.class).one().get();
	}
}
1 使用您需要的確切查詢或邏輯。

上面提到的生成器可以像這樣配置為一個 bean 引用

示例 6. 使用 Spring Bean 作為 ID 生成器的可變 MovieEntity
@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue(generatorRef = "myIdGenerator")
	private String id;

	private String name;
}

使用業務鍵

在完整示例的 MovieEntityPersonEntity 中,我們一直使用業務鍵。人員的姓名在構造時被賦值,無論是透過您的應用程式還是透過 Spring Data 載入時。

這隻有在您找到一個穩定、唯一的業務鍵時才可能實現,但這能建立優秀的不可變領域物件。

  • 優點:使用業務鍵或自然鍵作為主鍵是很自然的。相關的實體被清晰地標識,並且在進一步建模您的領域時,大多數時候感覺非常合適。

  • 缺點:一旦您意識到找到的鍵不像您想象的那麼穩定,將業務鍵用作主鍵將難以更新。通常會發現它可能會改變,即使之前承諾不會。除此之外,找到真正唯一標識事物的識別符號也很困難。

請記住,業務鍵總是在 Spring Data Neo4j 處理領域實體之前設定的。這意味著它無法確定實體是新的還是已存在的(它總是假定實體是新的),除非還提供了一個 @Version 欄位