在阅读Hibernate文档时,我总是看到对自然标识符的概念的引用。什么是Hibernate中的自然标识符?
这是否意味着实体所拥有的id由于其拥有的数据的性质?
E.g.用户的名字+密码+年龄+有些东西被用作复合标识符?
在阅读Hibernate文档时,我总是看到对自然标识符的概念的引用。什么是Hibernate中的自然标识符?
这是否意味着实体所拥有的id由于其拥有的数据的性质?
E.g.用户的名字+密码+年龄+有些东西被用作复合标识符?
在Hibernate中,自然键通常用于查找。在大多数情况下,您将拥有自动生成的代理ID。但是这个id对于查找来说毫无用处,因为你总是会查询字段,如姓名,社会安全号码或其他来自现实世界的其他字段。
当使用Hibernate的缓存功能时,这种差异非常重要:如果缓存由主键(代理id)索引,查找时不会有任何性能提升。这就是为什么您可以定义一组您要查询数据库的字段 - 自然ID。然后Hibernate可以通过自然键索引数据并提高查找性能。
看到这个优秀的blog post为更详细的解释或这RedHat page为例子Hibernate映射文件。
社会安全号码可能是一个natural identity,或者你已经说过了用户信息的散列。替代方案是surrogate key,例如Guid/UID。
自然标识符是在现实世界中用作标识符的东西。一个例子是社会安全号码或护照号码。
在持久层中使用自然标识符作为关键字通常是个坏主意,因为a)它们可以在您的控制之外进行更改,并且b)由于其他地方的错误,它们可能最终不会是唯一的,你的数据模型无法处理它,所以你的应用程序爆炸了。
人们希望关键是受到约束的,例如主键约束来减少这种风险 – gbn 2009-12-15 20:46:46
你可以将它约束在你的数据模型中,但是你不能限制现实生活 - 错误确实发生,并且您的数据模型在他们这样做时不需要中断。如果你需要纠正某人的SSN,因为例如它输入错误,它应该是一个单一的更新。如果你在整个系统中使用它作为密钥......将其序列化,将其存储在备份中,甚至可能将其发送到外部系统,那么你就完全搞砸了。你不可能在不破坏某些东西的情况下更新该人的SSN。 PS:除非必须,否则不要存储SSN。 – 2009-12-15 20:59:07
的确,它仍然需要约束,逻辑模型和实现应该有所不同。 SSN不是独一无二的... http://www.computerworld.com/s/article/300161/Not_So_Unique – gbn 2009-12-16 05:47:00
在关系数据库系统中,通常情况下,你可以有two types of simple identifiers:
为什么代理键如此受欢迎的原因是它们更紧凑(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
实体。很酷,对吧?
如果数据无法更改,那么散列(并且它不需要是散列,因为一个键可以是多列)只是一个有效的自然键(电子邮件很好,名称是iffy,密码是不太可能,年龄是错误的)。 – Tordek 2009-12-15 20:42:31
@Chris S:不对:“代孕” – gbn 2009-12-15 20:44:29
@Tordek:好点。 @Gbn稍微更新了文字。维基百科的文章实际上有很好的解释 – 2009-12-16 11:55:28