唯一ID的处理和配置

使用Neo4j内部ID

为您的领域类提供唯一标识符最简单的方法是结合使用类型为StringLong的字段上的@Id@GeneratedValue(最好是对象,而不是标量long,因为文字null是指示实例是新的还是旧的更好的指标)

示例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绑定,该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策略。一个通用唯一标识符旨在在实践中是唯一的。引用维基百科:“因此,任何人都可以创建一个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字段