在阅读 Hibernate 文档时,我不断看到对自然标识符概念的引用。
这是否仅仅意味着实体由于其所拥有数据的性质而具有的 id?
例如,用户名 + 密码 + 年龄 + 某物用作复合标识符?
在阅读 Hibernate 文档时,我不断看到对自然标识符概念的引用。
这是否仅仅意味着实体由于其所拥有数据的性质而具有的 id?
例如,用户名 + 密码 + 年龄 + 某物用作复合标识符?
在 Hibernate 中,自然键通常用于查找。在大多数情况下,您将拥有一个自动生成的代理 ID。但是这个 id 对查找毫无用处,因为您总是会通过姓名、社会安全号码或来自现实世界的任何其他字段进行查询。
在使用 Hibernate 的缓存特性时,这种差异非常重要:如果缓存是由您的主键(代理 id)索引的,则查找时不会有任何性能提升。这就是为什么您可以定义一组要用来查询数据库的字段 - 自然 id。然后 Hibernate 可以通过您的自然键索引数据并提高查找性能。
有关更详细的说明,请参阅此出色的博客文章,或参阅此RedHat 页面以获取示例 Hibernate 映射文件。
在关系数据库系统中,通常可以有两种类型的简单标识符:
IDENTITY
或SEQUENCE
由数据库分配。代理键如此受欢迎的原因是它们更紧凑(4 字节或 8 字节),与非常长的自然键(例如 VIN 需要 17 个字母数字字符,书的 ISBN 长度为 13 位)相比。如果代理键成为主键,您可以使用 JPA@Id
注释对其进行映射。
现在,假设我们有以下Post
实体:
由于Post
实体也具有自然键,除了代理项之外,您可以使用特定于 Hibernate 的@NaturalId
注释对其进行映射:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@NaturalId
@Column(nullable = false, unique = true)
private String slug;
//Getters and setters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
Post post = (Post) o;
return Objects.equals(slug, post.slug);
}
@Override
public int hashCode() {
return Objects.hash(slug);
}
}
现在,考虑到上面的实体,用户可能已经为Post
一篇文章添加了书签,现在他们想要阅读它。但是,添加书签的 URL 包含slug
自然标识符,而不是主键。
因此,我们可以使用 Hibernate 像这样获取它:
Post post = entityManager.unwrap(Session.class)
.bySimpleNaturalId(Post.class)
.load(slug);
在 Hibernate 5.5 或更高版本上通过其自然键获取实体时,将生成以下 SQL 查询:
SELECT p.id AS id1_0_0_,
p.slug AS slug2_0_0_,
p.title AS title3_0_0_
FROM post p
WHERE p.slug = 'high-performance-java-persistence'
因此,从 Hibernate 5.5 开始,实体通过其自然标识符直接从数据库中获取。
在 Hibernate 5.4 或更早版本上通过其自然键获取实体时,会生成两个 SQL 查询:
SELECT p.id AS id1_0_
FROM post p
WHERE p.slug = 'high-performance-java-persistence'
SELECT p.id AS id1_0_0_,
p.slug AS slug2_0_0_,
p.title AS title3_0_0_
FROM post p
WHERE p.id = 1
需要第一个查询来解析与提供的自然标识符相关联的实体标识符。
如果实体已加载到第一级或第二级缓存中,则第二个查询是可选的。
有第一个查询的原因是因为 Hibernate 已经有一个完善的逻辑来通过它们在持久性上下文中的标识符来加载和关联实体。
现在,如果您想跳过实体标识符查询,您可以使用注解轻松地对实体进行@NaturalIdCache
注解:
@Entity(name = "Post")
@Table(name = "post")
@org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE
)
@NaturalIdCache
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@NaturalId
@Column(nullable = false, unique = true)
private String slug;
//Getters and setters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
Post post = (Post) o;
return Objects.equals(slug, post.slug);
}
@Override
public int hashCode() {
return Objects.hash(slug);
}
}
这样,您Post
甚至可以在不访问数据库的情况下获取实体。很酷,对吧?
自然标识符是在现实世界中用作标识符的东西。一个例子是社会安全号码或护照号码。
在持久层中使用自然标识符作为键通常是一个坏主意,因为 a) 它们可以在您的控制之外进行更改,并且 b) 由于其他地方的错误,它们最终可能不是唯一的,然后您的数据模型可以'不要处理它,所以你的应用程序爆炸了。
在关系数据库理论中,一个关系可以有多个候选键。候选键是关系的一组属性,它们在该关系的两行中永远不会重复,并且不能通过删除其中一个属性来减少并且仍然保证唯一性。
自然 ID 本质上是候选键。其中“自然”意味着它是您在该关系中持有的数据的性质,而不是您添加的自动生成密钥之类的东西。自然 ID 可以由单个属性组成。通常,关系的任何唯一且不为空的属性都是候选键,并且可以被视为自然 id。
在 Hibernate 中,这个注解可以简单地用来表示一个属性可以用来进行搜索,在不使用键的情况下返回唯一的结果。当您表示为自然 id 的属性更自然地为您处理时,这可能很有用,例如,当实际键是自动生成的并且您不想在搜索中使用时。
自然标识符(也称为业务密钥):是表示或代表现实生活中某事的标识符。
人的
电子邮件或国民身份证Isbn为银行帐户预订IBAN
此@NaturalId
Annotation 用于指定自然标识符。