Handling and provisioning of unique IDs

为 Neo4j 域实体提供唯一标识符有三种主要方法:

  • 使用内部生成的 ID:由 Neo4j 管理,简单易用,但与数据库 ID 绑定,并且无法生成不可变实体。

  • 使用外部提供的替代密钥:由应用程序控制,生成唯一密钥,但可能在多个应用程序实例中导致重复。

  • 使用业务密钥:由应用程序分配,易于理解,但难以更新,并且可能难以找到真正唯一的标识符。

Using the internal Neo4j id

为您的域类提供唯一标识符的最简单方法是将 @Id@GeneratedValue 与类型为 StringLong 的字段结合使用(首选对象而非标量 long,因为文字 null 是一个更好的指示符,可以判断一个实例是否是新的):

Example 1. Mutable MovieEntity with internal Neo4j id
@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue
	private Long id;

	private String name;

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

您不需要为该字段提供一个设置器,SDN 将使用反射来分配该字段,但如果存在设置器,请使用该设置器。如果您希望使用内部生成 ID 创建一个不可变实体,则必须提供一个 wither

Example 2. Immutable MovieEntity with internal Neo4j id
@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 不变的最终 id 字段表示生成的值
2 公共构造器,由应用程序和 Spring Data 使用
3 Internally used constructor
4 对于 id -attribute,这是一个所谓的 wither。它创建一个新实体并相应地设置字段,而无需修改原始实体,从而使其不可变。

如果你想提供一个设置器来设置 id 属性或者像 wither 这样的东西,则:

  • 优点:很明显,id 属性是替代业务键,它无需进一步的努力或配置即可使用它。

  • 缺点:它与 Neo4js 内部数据库 id 相关联,该 id 仅在数据库生命周期内对我们的应用程序实体是唯一的。

  • 缺点:创建一个不可变的实体需要更多的工作

Use externally provided surrogate keys

@GeneratedValue 注释可以采用一个实现 org.springframework.data.neo4j.core.schema.IdGenerator 的类作为参数。SDN 提供了 InternalIdGenerator(默认)和 UUIDStringGenerator 开箱即用。后者为每个实体生成新的 UUID,并将其作为 java.lang.String 返回。使用它的应用程序实体看起来像这样:

Example 3. Mutable MovieEntity with externally generated surrogate key
@Node("Movie")
public class MovieEntity {

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

	private String name;
}

我们必须讨论关于优势和劣势的两个不同方面。分配本身和 UUID 策略。 universally unique identifier在实践中是为了唯一。援引 Wikipedia:“因此,任何人都可以创建 UUID 并使用它来识别某物,几乎可以确定该标识符不会重复已经或将要为识别某物而创建的一个。”我们的策略使用 Java 内部 UUID 机制,采用加密强伪随机数生成器。在大多数情况下,这应该可以正常工作,但您的里程可能有所不同。

这就剩下分配本身了:

  • 优点:应用程序完全受控,可以生成一个唯一的键,该键对于应用程序的目的来说足够唯一。生成的值将是稳定的,以后无需对其进行更改。

  • 缺点:生成策略应用于事物的应用程序端。在那些日子里,大多数应用程序将部署在多个实例中以很好地扩展。如果你的策略容易生成重复项,那么插入将失败,因为主键的唯一性属性将被违反。因此,虽然在这种情况下你不必考虑唯一的业务键,但你必须更多地考虑要生成的内容。

你有几个选项可以推出自己的 ID 生成器。一个是一个实现生成器的 POJO:

Example 4. Naive sequence generator
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:

Example 5. Neo4jClient based ID generator
@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 引用:

Example 6. Mutable MovieEntity using a Spring Bean as Id generator
@Node("Movie")
public class MovieEntity {

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

	private String name;
}

Using a business key

我们一直在 MovieEntity 和 xref:object-mapping/metadata-based-mapping.adoc#mapping.complete-example.person[PersonEntity 中的完整示例中使用业务密钥。无论是在构建时还是在通过 Spring Data 加载时,该人名都会在构建时分配。

这只有在你找到一个稳定的唯一业务键时才有可能,但会生成非常好的不可变域对象。

  • 优点:将业务或自然键用作主键是自然的。有问题的实体被明确识别,并且在你的域的进一步建模中绝大多数时候感觉正确。

  • 缺点:一旦你意识到你发现的键不像你想象的那么稳定,业务键作为主键将很难更新。通常事实证明它可以改变,即使在做出相反的承诺时也是如此。除此之外,找到真正唯一标识事物的标识符很困难。

请记住,在 Spring Data Neo4j 处理业务密钥之前,它始终会设置在域实体上。这意味着它无法确定该实体是否为新实体(它始终假设该实体为新实体),除非还提供了 xref:object-mapping/metadata-based-mapping.adoc#mapping.annotations.version[@Version 字段。