什么是休眠中的自然标识符?
在阅读Hibernate文档时,我不断看到对自然标识符概念的引用。
这是否仅仅意味着实体由于其所持有的数据的性质而拥有的ID?
例如,用户名 + 密码 + 年龄 + 某些东西被用作复合标识器?
在阅读Hibernate文档时,我不断看到对自然标识符概念的引用。
这是否仅仅意味着实体由于其所持有的数据的性质而拥有的ID?
例如,用户名 + 密码 + 年龄 + 某些东西被用作复合标识器?
在关系数据库系统中,通常,可以有两种类型的简单标识符:
IDENTITY
SEQUENCE
代理密钥如此受欢迎的原因是它们更紧凑(4字节或8字节),而自然密钥很长(例如,VIN需要17个字母数字字符,书ISBN的长度为13位)。如果代理项成为主键,则可以使用 JPA 注释对其进行映射。@Id
现在,让我们假设我们有以下实体:Post
由于实体还具有自然键,因此除了代理键之外,您还可以使用特定于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 包含自然标识符,而不是主键。Post
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