什么是休眠中的自然标识符?

2022-09-01 04:55:30

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

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

例如,用户名 + 密码 + 年龄 + 某些东西被用作复合标识器?


答案 1

在休眠中,自然键通常用于查找。在大多数情况下,您将拥有一个自动生成的代理项 ID。但是这个ID对于查找是相当无用的,因为你总是会按名称,社会安全号码或现实世界中的其他任何内容等字段进行查询。

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

有关更详细的说明,请参阅此优秀博客文章,或查看此 RedHat 页面以获取示例 Hibernate 映射文件。


答案 2

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

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

代理密钥如此受欢迎的原因是它们更紧凑(4字节或8字节),而自然密钥很长(例如,VIN需要17个字母数字字符,书ISBN的长度为13位)。如果代理项成为主键,则可以使用 JPA 注释对其进行映射。@Id

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

Post entity with natural id

由于实体还具有自然键,因此除了代理键之外,您还可以使用特定于Hibernate的注释对其进行映射:Post@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);
    }
}

现在,考虑到上面的实体,用户可能已经为一篇文章添加了书签,现在他们想要阅读它。但是,已添加书签的 URL 包含自然标识符,而不是主键。Postslug

因此,我们可以使用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


推荐