唯一 ID 的处理和提供
使用内部 Neo4j ID
为您的域类提供唯一标识符的最简单方法是在 String 或 Long 类型的字段上结合使用 @Id 和 @GeneratedValue(最好是对象类型,而不是标量 long,因为字面量 null 是判断实例是否为新的更好指标)。
@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。
@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 返回。使用它的应用程序实体将如下所示:
@Node("Movie")
public class MovieEntity {
@Id @GeneratedValue(UUIDStringGenerator.class)
private String id;
private String name;
}
我们需要讨论关于优点和缺点的两个不同方面。赋值本身和 UUID 策略。一个 通用唯一标识符 旨在在实际应用中是唯一的。引用维基百科:“因此,任何人都可以创建一个 UUID 并使用它来识别某物,几乎可以肯定该标识符不会与已经创建或将要创建的用于识别其他事物的标识符重复。” 我们的策略使用 Java 内部的 UUID 机制,采用密码学上强大的伪随机数生成器。在大多数情况下,这应该没有问题,但效果可能会因情况而异。
这就引出了赋值本身的问题
-
优点:应用程序完全可控,可以生成一个对于应用程序目的来说足够唯一的键。生成的值将是稳定的,并且以后不需要更改。
-
缺点:生成策略是在应用程序端应用的。如今,大多数应用程序将部署在多个实例中以实现良好的扩展性。如果您的策略容易生成重复项,那么插入将失败,因为主键的唯一性属性将被违反。因此,在这种情况下,虽然您不必考虑唯一的业务键,但您必须更多地考虑要生成什么。
您有几种选项可以实现自己的 ID 生成器。其中之一是实现生成器的 POJO。
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,如下所示:
@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 引用,如下所示:
@Node("Movie")
public class MovieEntity {
@Id @GeneratedValue(generatorRef = "myIdGenerator")
private String id;
private String name;
}
使用业务键
我们在完整示例的 MovieEntity 和 PersonEntity 中使用了业务键。人物的名称在构建时分配,无论是通过您的应用程序还是通过 Spring Data 加载时。
这只有在您找到一个稳定、唯一的业务键时才可能实现,但它能创建出色的不可变域对象。
-
优点:使用业务键或自然键作为主键是很自然的。所讨论的实体被清晰地标识,并且在进一步的领域建模中,它在大多数情况下都感觉非常合适。
-
缺点:当您发现所找到的键不如您想象的稳定时,作为主键的业务键将很难更新。通常事实证明它会改变,即使承诺不会。除此之外,找到真正唯一的标识符也很困难。
请记住,业务键总是在 Spring Data Neo4j 处理之前在域实体上设置。这意味着它无法确定实体是新的还是旧的(它总是假设实体是新的),除非还提供了 @Version 字段。