2009-12-15 63 views
10

在阅读Hibernate文档时,我总是看到对自然标识符的概念的引用。什么是Hibernate中的自然标识符?

这是否意味着实体所拥有的id由于其拥有的数据的性质?

E.g.用户的名字+密码+年龄+有些东西被用作复合标识符?

回答

11

在Hibernate中,自然键通常用于查找。在大多数情况下,您将拥有自动生成的代理ID。但是这个id对于查找来说毫无用处,因为你总是会查询字段,如姓名,社会安全号码或其他来自现实世界的其他字段。

当使用Hibernate的缓存功能时,这种差异非常重要:如果缓存由主键(代理id)索引,查找时不会有任何性能提升。这就是为什么您可以定义一组您要查询数据库的字段 - 自然ID。然后Hibernate可以通过自然键索引数据并提高查找性能。

看到这个优秀的blog post为更详细的解释或这RedHat page为例子Hibernate映射文件。

8

自然识别实体的内容。例如,我的电子邮件地址。

但是,长期以来可变长度的字符串是不是一个理想的关​​键,所以你可能要在关系设计定义surrogate id

AKA Natural key

2

社会安全号码可能是一个natural identity,或者你已经说过了用户信息的散列。替代方案是surrogate key,例如Guid/UID。

+0

如果数据无法更改,那么散列(并且它不需要是散列,因为一个键可以是多列)只是一个有效的自然键(电子邮件很好,名称是iffy,密码是不太可能,年龄是错误的)。 – Tordek 2009-12-15 20:42:31

+0

@Chris S:不对:“代孕” – gbn 2009-12-15 20:44:29

+0

@Tordek:好点。 @Gbn稍微更新了文字。维基百科的文章实际上有很好的解释 – 2009-12-16 11:55:28

5

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

在持久层中使用自然标识符作为关键字通常是个坏主意,因为a)它们可以在您的控制之外进行更改,并且b)由于其他地方的错误,它们可能最终不会是唯一的,你的数据模型无法处理它,所以你的应用程序爆炸了。

+0

人们希望关键是受到约束的,例如主键约束来减少这种风险 – gbn 2009-12-15 20:46:46

+1

你可以将它约束在你的数据模型中,但是你不能限制现实生活 - 错误确实发生,并且您的数据模型在他们这样做时不需要中断。如果你需要纠正某人的SSN,因为例如它输入错误,它应该是一个单一的更新。如果你在整个系统中使用它作为密钥......将其序列化,将其存储在备份中,甚至可能将其发送到外部系统,那么你就完全搞砸了。你不可能在不破坏某些东西的情况下更新该人的SSN。 PS:除非必须,否则不要存储SSN。 – 2009-12-15 20:59:07

+1

的确,它仍然需要约束,逻辑模型和实现应该有所不同。 SSN不是独一无二的... http://www.computerworld.com/s/article/300161/Not_So_Unique – gbn 2009-12-16 05:47:00

1

在关系数据库系统中,通常情况下,你可以有two types of simple identifiers

  • 自然键,这是由外部系统分配,并且保证是唯一
  • 代理键,又像被分配IDENTITY or SEQUENCE数据库。

为什么代理键如此受欢迎的原因是它们更紧凑(4字节或8字节),相比之下很长的自然键(例如,VIN需要17个字母数字字符,书籍ISBN是13位数字)。

现在,如果代理密钥成为主键,则使用JPA @Id注释来使用它。

而且,如果你有一个具有自然也是主要的实体,除了代孕一个,你可以map it with the Hibernate-specific @NaturalId annotation

@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包含自然标识符,而不是主键。

因此,我们可以这样使用Hibernate取,

Post post = entityManager.unwrap(Session.class) 
.bySimpleNaturalId(Post.class) 
.load(slug); 

,Hibernate会执行以下两个查询:

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 

需要第一个查询,以解决相关的实体标识符提供的自然标识符。

如果实体已经加载到第一级或第二级缓存中,则第二个查询是可选的。

正如我在this article中解释的那样,第一个查询的原因是因为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实体。很酷,对吧?

相关问题