Hibernateのドキュメントを読んでいる間、私は自然な識別子の概念への参照を見続けています。
これは、エンティティが保持するデータの性質により、エンティティが持つIDを意味するだけですか?
例:ユーザーの名前+パスワード+年齢+何かが複合識別子として使用されていますか?
Hibernateのドキュメントを読んでいる間、私は自然な識別子の概念への参照を見続けています。
これは、エンティティが保持するデータの性質により、エンティティが持つIDを意味するだけですか?
例:ユーザーの名前+パスワード+年齢+何かが複合識別子として使用されていますか?
Hibernateでは、ルックアップに自然キーがよく使用されます。ほとんどの場合、自動生成された代理IDがあります。ただし、このIDは、名前、社会保障番号、その他の現実世界のフィールドで常にクエリを実行するため、ルックアップにはあまり役に立ちません。
Hibernateのキャッシング機能を使用する場合、この違いは非常に重要です。キャッシュが主キー(サロゲートID)によってインデックス付けされている場合、ルックアップのパフォーマンスは向上しません。そのため、データベースにクエリを実行する一連のフィールド(自然ID)を定義できます。Hibernateは、自然キーによってデータにインデックスを付け、ルックアップパフォーマンスを向上させることができます。
詳細な説明については、この優れたブログ投稿を参照してください。Hibernateマッピングファイルの例については、このRedHatページを参照してください。
リレーショナルデータベースシステムでは、通常、次の2種類の単純な識別子を使用できます。
IDENTITY
データベースによって割り当てられた、またはデータベースによって割り当てられた代理キーSEQUENCE
。サロゲートキーが非常に人気がある理由は、非常に長い自然キー(たとえば、VINは17文字の英数字を使用し、本のISBNは13桁の長さ)と比較して、よりコンパクト(4バイトまたは8バイト)であるためです。サロゲートキーがプライマリキーになる場合は、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);
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以前で自然キーを使用してエンティティをフェッチすると、次の2つの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
最初のクエリは、提供された自然識別子に関連付けられたエンティティ識別子を解決するために必要です。
エンティティがすでに第1レベルまたは第2レベルのキャッシュにロードされている場合、2番目のクエリはオプションです。
最初のクエリを使用する理由は、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
このようにして、データベースにアクセスすることなくエンティティをフェッチできます。かっこいいですよね?
自然な識別子は、現実の世界で識別子として使用されるものです。例としては、社会保障番号やパスポート番号があります。
通常、永続性レイヤーのキーとして自然識別子を使用することはお勧めできません。これは、a)制御外で変更される可能性があり、b)他の場所でのミスにより一意でなくなる可能性があるため、データモデルがアプリケーションが爆発するように処理しないでください。
リレーショナルデータベース理論では、リレーションは複数の候補キーを持つことができます。候補キーは、リレーションの2つの行で重複することはなく、属性の1つを削除しても削減できず、一意性を保証するリレーションの属性のセットです。
自然IDは本質的に候補キーです。「自然」とは、自動生成されたキーのように追加するものではなく、その関係で保持するデータの性質にあることを意味します。自然IDは、単一の属性で構成できます。一般に、一意でnullではないリレーションの属性は候補キーであり、自然IDと見なすことができます。
Hibernateでは、このアノテーションを使用して、キーを使用せずに一意の結果を返す検索を実行するために属性を使用できることを示すことができます。これは、自然IDとして指定する属性がより自然に処理できる場合、たとえば、実際のキーが自動生成され、検索で使用したくない場合に役立ちます。
自然識別子(ビジネスキーとも呼ばれます):実生活で何かを意味または表す識別子です。
銀行口座のブックIBANの個人Isbnの
電子メールまたは国民ID
この@NaturalId
注釈は、Natural識別子を指定するために使用されます。