Hibernate中的自然标识符是什么?

41

在阅读Hibernate文档时,我一直看到对自然标识符这个概念的引用。

这是否意味着实体由于其所持有的数据的本质而具有的id?

例如,用户的姓名+密码+年龄+某些内容作为复合标识符使用?

7个回答

47
在Hibernate中,常常使用自然键进行查找。在大多数情况下,您都会有一个自动生成的代理id。但是,这个id对于查找来说相当无用,因为您总是会通过名字、社会安全号码或来自现实世界的其他任何字段进行查询。
当使用Hibernate的缓存功能时,这种差异非常重要:如果缓存以您的主键(代理id)索引,则在查找时不会有任何性能提升。这就是为什么您可以定义一组要使用的字段来查询数据库 - 自然id。Hibernate可以通过您的自然键索引数据并提高查找性能。
更详细的解释请参见这篇优秀的博客文章,或者参见这个RedHat页面获取Hibernate映射文件示例。

27

在关系型数据库系统中,通常可以有两种类型的简单标识符:

  • 自然键,由外部系统分配并保证唯一性
  • 代理键(如 IDENTITYSEQUENCE),由数据库分配。

代理键之所以如此流行,是因为它们更紧凑(4字节或8字节),而自然键非常长(例如,车架号需要17个字符,书籍ISBN是13位数字)。如果将代理键作为主键,则可以使用JPA @Id 注释进行映射。

现在,假设我们有以下Post 实体:

带自然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或更高版本

在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或更早版本

在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实体。很酷,对吧?


抱歉评论了一个旧问题。但是,我不明白“跳过实体标识符查询”是什么意思? - Amir Choubani
1
这意味着跳过此查询:SELECT p.id AS id1_0_ FROM post p WHERE p.slug = 'high-performance-java-persistence'该查询旨在获取特定自然标识值的实体标识符。 - Vlad Mihalcea
1
你好,使用"id"字段是必要的吗?我们不能只使用slug作为Id和NaturalId吗? - Khatri
好的例子,谢谢。 1. 每当我们使用 @NaturalId 时,我们是否必须添加 @NaturalIdCache 注释? 2. 就我所见,@NaturalId 注释用作索引,但我不确定在实体中有唯一字段时是否应该使用它。 有什么想法吗? 3. 当属性可更新或不可更新时,我可以使用 @NaturalId 吗? - user19601508

10

自然标识是指在现实世界中用作标识符的东西。例如,社会安全号码或护照号码。

通常情况下,在持久层中使用自然标识作为键是一个不好的想法,因为a)它们可能会在你无法控制的情况下被更改,b)由于其他地方的错误而导致它们不唯一,这样你的数据模型就无法处理,从而导致应用程序崩溃。


希望关键字受到限制,例如主键约束,以减少此类风险。 - gbn
3
你可以在数据模型中对其进行限制,但是现实生活中无法约束 - 错误是难免的,当出现错误时,你的数据模型不需要崩溃。如果你需要更正某人的社会安全号码,因为例如输入错误,应该只需进行一次更新。如果你已经在整个系统中使用它作为关键字...序列化它,存储它在备份中,甚至将其发送到外部系统,那么你就完全失败了。没有办法更新该人的社会安全号码而不破坏任何东西。附注:除非必须,否则根本不要存储社会安全号码。 - Mark Byers
1
是的,它仍然需要受限制,并且逻辑模型和实现之间应该有所区别。社会安全号码也不是唯一的... http://www.computerworld.com/s/article/300161/Not_So_Unique - gbn

8

什么是自然标识符,例如我的电子邮件地址。

然而,一个长度较长的变量字符串并不是一个理想的键,因此您可能需要定义一个替代标识

也称为关系设计中的自然键


2
社会保障号码可能是一种自然身份标识,或者就像你所说的用户信息的哈希值。另一种选择是代理键,例如GUID / UID。

哈希(它不需要是哈希,因为一个键可以是多列)只有在数据不能更改时才是有效的自然键(电子邮件没问题,姓名有点棘手,密码不太可能,年龄也不对)。 - Tordek
@Chris S:不是相反的意思:“代理”。 - gbn
@Tordek:说得好。@Gbn稍微更新了一下文本。维基百科的文章实际上对两者都有很好的解释。 - Chris S

2
在关系数据库理论中,一个关系可以有多个候选键。候选键是一个关系的属性集合,这些属性在该关系的两行中永远不会重复,并且删除其中一个属性仍然保证唯一性。
自然ID本质上是一个候选键。其中,“自然”表示它是您在该关系中保存的数据的本质,而不是像自动生成的键那样添加的内容。自然ID可以由单个属性组成。通常,关系的任何唯一且非空的属性都是候选键,可以被视为自然ID。
在Hibernate中,此注释可用于简单地表示可以使用属性进行搜索,以返回唯一结果,同时不使用键。当您指定为自然ID的属性更自然易处理时,例如,实际键是自动生成的,并且您不想在搜索中使用它时,这可能很有用。

1
自然标识符(也称业务键):是指在现实生活中具有意义或代表某物的标识符。
个人的电子邮件或国民身份证号码
书籍的ISBN号码
银行账户的IBAN号码

这个@NaturalId注释用于指定自然标识符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接