【问题标题】:What is a natural identifier in Hibernate?Hibernate 中的自然标识符是什么?
【发布时间】:2010-12-26 23:35:30
【问题描述】:

在阅读 Hibernate 文档时,我不断看到对自然标识符概念的引用。

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

例如用户名+密码+年龄+某物作为复合标识符?

【问题讨论】:

    标签: java hibernate jpa orm natural-key


    【解决方案1】:

    在 Hibernate 中,自然键通常用于查找。在大多数情况下,您将拥有一个自动生成的代理 ID。但是这个 id 对查找没什么用,因为您总是会通过姓名、社会保险号或来自现实世界的任何其他字段进行查询。

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

    请参阅此出色的 blog post 以获得更详细的说明,或参阅此 RedHat page 以获得示例 Hibernate 映射文件。

    【讨论】:

      【解决方案2】:

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

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

      代理键如此受欢迎的原因是它们更紧凑(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); 
      

      休眠 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 开始,实体通过其自然标识符直接从数据库中获取。

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

      【讨论】:

      • 很抱歉评论一个老问题。但是,我不明白skip the entity identifier query 是什么意思?
      • 这意味着跳过此查询: SELECT p.id AS id1_0_ FROM post p WHERE p.slug = 'high-performance-java-persistence' 旨在获取此 parituvlar natural-id 的实体标识符价值。
      • 你好,是否有必要使用“id”字段,我们不能只使用 slug 作为 Id 和 NaturalId 吗?
      【解决方案3】:

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

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

      【讨论】:

      • 希望对键进行约束,例如主键约束,以减少此类风险
      • 你可以在你的数据模型中约束它,但你不能约束现实生活——错误确实会发生,当它们发生时你的数据模型不需要破坏。如果您需要更正某人的 SSN,例如因为输入不正确,则应该是单个 UPDATE。如果您在整个系统中使用它作为密钥......对其进行序列化,将其存储在备份中,甚至可能将其发送到外部系统,那么您完全搞砸了。您无法在不破坏某些内容的情况下更新该人的 SSN。 PS:除非万不得已,否则不要存储 SSN。
      • 没错,它仍然需要约束,逻辑模型和实现之间应该存在差异。 SSN 也不是唯一的...computerworld.com/s/article/300161/Not_So_Unique
      【解决方案4】:

      什么自然地标识一个实体。例如,我的电子邮件地址。

      然而,一个长的可变长度字符串不是一个理想的键,所以你可能想定义一个surrogate id

      在关系设计中又名Natural key

      【讨论】:

        【解决方案5】:

        社会保险号可能是natural identity,或者如您所说的用户信息的哈希值。替代方法是 surrogate key,例如 Guid/UID。

        【讨论】:

        • 散列(它不需要是散列,因为一个键可以是多列)只有在数据不能改变时才是有效的自然键(电子邮件很好,名字不确定,密码不太可能并且年龄错误)。
        • @Chris S:不相反:“代理”
        • @Tordek:好点。 @Gbn 稍微更新了文本。维基百科的文章实际上对两者都有很好的解释
        【解决方案6】:

        在关系数据库理论中,一个关系可以有多个候选键candidate key 是关系的一组属性,它们在该关系的两行中永远不会重复,并且不能通过删除其中一个属性来减少,并且仍然保证唯一性。

        自然 ID 本质上是候选键。其中“自然”意味着它是您在该关系中持有的数据的性质,而不是您添加的自动生成密钥之类的东西。 自然 ID 可以由单个属性组成。 一般来说,关系的任何唯一且不为空的属性都是候选键,可以认为是自然 id。

        在 Hibernate 中,这个注解可以简单地用来表示一个属性可以用来进行搜索,在不使用键的情况下返回唯一的结果。 当您表示为自然 id 的属性更自然地为您处理时,这可能很有用,例如当实际密钥是自动生成的并且您不想在搜索中使用时。

        【讨论】:

          【解决方案7】:

          自然标识符(也称为业务密钥):是表示或代表现实生活中某事的标识符。
          电子邮件国民身份证用于
          Isbn用于图书
          IBAN用于银行帐户

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-09-17
            • 2018-09-22
            • 1970-01-01
            • 1970-01-01
            • 2016-11-16
            • 1970-01-01
            • 2017-05-13
            相关资源
            最近更新 更多