唯一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方法,Spring Data Neo4j(SDN)将使用反射来赋值该字段,但如果存在setter方法,则会优先使用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 | Public 构造函数,供应用程序和 Spring 数据使用 |
| 3 | 内部使用的构造函数 |
| 4 | 这是一个所谓的wither方法,它为id属性提供支持。它会创建一个新的实体,并按照要求设置该字段,而不修改原始实体,从而使其不可变。 |
您要么必须为 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-Strategy。通用唯一标识符旨在在实际目的上是唯一的。引用维基百科:“因此,任何人都可以创建一个UUID,并使用它来几乎肯定地对某事物进行编号,该编号不会重复已经创建或将来创建的其他标识符。”我们的策略使用Java内部UUID机制,采用加密强伪随机数生成器。在大多数情况下,这应该没问题,但你的里程可能会有所不同。
这就留下了作业本身:
-
优势:应用程序完全受控,可以生成一个唯一的键,该键仅适合应用程序目的。生成的值将是稳定的,以后不需要更改。
-
失利之处:所生成的策略将会运用到事物的使用端层面。 在那些日子,大多数应用都将在多于一个实例上进行部署,以便更好地扩展。 如果你的策略容易产生重复项,那么插入操作就会失败,因为主键的唯一性属性会被违反。 所以,在这种情况下,虽然你不必思考独特的业务键,但你必须考虑要生成什么。
你有几种选项可以推出你自己的生成器。一种是实现一个生成器的 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加载。
只有在你找到一个稳定、唯一的业务键时,这才可能实现,但这样可以创建出非常不可变的领域对象。
-
优势:使用业务键或自然键作为主键是自然的。实体被明确标识,并且在大多数情况下,在进一步建模您的域时感觉最正确。
-
缺点:业务密钥作为主密钥一旦发现不是像你想象的那样稳定,就很难更新。 <br/> 通常会发现它可能会更改,即使承诺并非如此。 <br/> 除了这个之外,找到真正对于一个事物唯一的标识符是很困难的。
请注意,在域实体上 always 设置业务键之前,Spring Data Neo4j 处理它时,无法判断该实体是否为新实体(它总是假设该实体是新的),除非也提供了一个 @Version 字段。