Hibernateの識別子の概要
1. 前書き
Hibernateの識別子は、エンティティの主キーを表します。 これは、値が一意であるため、特定のエンティティを識別でき、nullではなく、変更されないことを意味します。
Hibernateは、識別子を定義するいくつかの異なる方法を提供します。 この記事では、ライブラリを使用してエンティティIDをマッピングする各方法を確認します。
2. 単純な識別子
識別子を定義する最も簡単な方法は、@Idアノテーションを使用することです。
単純なIDは、@Idを使用して、次のいずれかのタイプの単一のプロパティにマップされます。Javaプリミティブおよびプリミティブラッパータイプ、String, Date, BigDecimal, BigInteger.
タイプlong:の主キーを使用してエンティティを定義する簡単な例を見てみましょう。
@Entity
public class Student {
@Id
private long studentId;
// standard constructor, getters, setters
}
3. 生成された識別子
主キー値を自動的に生成する場合は、we can add the @GeneratedValue annotationを使用します。
これには、AUTO、IDENTITY、SEQUENCE、TABLEの4つの世代タイプを使用できます。
値を明示的に指定しない場合、生成タイプはデフォルトでAUTOになります。
3.1. AUTO世代
デフォルトの生成タイプを使用している場合、永続性プロバイダーは主キー属性のタイプに基づいて値を決定します。 このタイプは数値またはUUID.にすることができます
数値の場合、生成はシーケンスまたはテーブルジェネレータに基づいていますが、UUID値はUUIDGenerator.を使用します
AUTO生成戦略を使用してエンティティの主キーをマッピングする例を見てみましょう。
@Entity
public class Student {
@Id
@GeneratedValue
private long studentId;
// ...
}
この場合、主キーの値はデータベースレベルで一意になります。
An interesting feature introduced in Hibernate 5 is the UUIDGenerator.これを使用するには、@GeneratedValueアノテーションを付けてタイプUUIDのIDを宣言するだけです。
@Entity
public class Course {
@Id
@GeneratedValue
private UUID courseId;
// ...
}
Hibernateは、「8dd5f315-9788-4d00-87bb-10eed9eff566」という形式のIDを生成します。
3.2. IDENTITY世代
このタイプの生成は、データベースのidentity列によって生成された値を期待するIdentityGeneratorに依存します。つまり、値は自動インクリメントされます。
この生成タイプを使用するには、strategyパラメータを設定するだけです。
@Entity
public class Student {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private long studentId;
// ...
}
注意すべきことの1つは、IDENTITY生成によりバッチ更新が無効になることです。
3.3. SEQUENCE世代
シーケンスベースのIDを使用するために、HibernateはSequenceStyleGeneratorクラスを提供します。
このジェネレーターは、データベースでサポートされている場合はシーケンスを使用し、サポートされていない場合はテーブル生成に切り替えます。
シーケンス名をカスタマイズするには、SequenceStyleGenerator strategy:で@GenericGeneratorアノテーションを使用できます
@Entity
public class User {
@Id
@GeneratedValue(generator = "sequence-generator")
@GenericGenerator(
name = "sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "user_sequence"),
@Parameter(name = "initial_value", value = "4"),
@Parameter(name = "increment_size", value = "1")
}
)
private long userId;
// ...
}
この例では、シーケンスの初期値も設定しました。これは、主キーの生成が4から始まることを意味します。
SEQUENCEは、Hibernateドキュメントで推奨されている世代タイプです。
The generated values are unique per sequence.シーケンス名を指定しない場合、Hibernateは異なるタイプに同じhibernate_sequenceを再利用します。
3.4. テーブルの生成
TableGeneratorは、識別子生成値のセグメントを保持する基礎となるデータベーステーブルを使用します。
@TableGeneratorアノテーションを使用してテーブル名をカスタマイズしましょう。
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "table-generator")
@TableGenerator(name = "table-generator",
table = "dep_ids",
pkColumnName = "seq_id",
valueColumnName = "seq_value")
private long depId;
// ...
}
この例では、pkColumnNameやvalueColumnNameなどの他の属性もカスタマイズできることがわかります。
この方法の欠点は、拡張性が低く、パフォーマンスに悪影響を与える可能性があることです。
要約すると、これらの4つの生成タイプでは、同様の値が生成されますが、異なるデータベースメカニズムが使用されます。
3.5. カスタムジェネレーター
すぐに使用できる戦略を使用したくない場合は、we can define our custom generator by implementing the IdentifierGenerator interfaceを使用します。
Stringプレフィックスと数値を含む識別子を作成するジェネレータを作成しましょう。
public class MyGenerator
implements IdentifierGenerator, Configurable {
private String prefix;
@Override
public Serializable generate(
SharedSessionContractImplementor session, Object obj)
throws HibernateException {
String query = String.format("select %s from %s",
session.getEntityPersister(obj.getClass().getName(), obj)
.getIdentifierPropertyName(),
obj.getClass().getSimpleName());
Stream ids = session.createQuery(query).stream();
Long max = ids.map(o -> o.replace(prefix + "-", ""))
.mapToLong(Long::parseLong)
.max()
.orElse(0L);
return prefix + "-" + (max + 1);
}
@Override
public void configure(Type type, Properties properties,
ServiceRegistry serviceRegistry) throws MappingException {
prefix = properties.getProperty("prefix");
}
}
この例では、we override the generate() method from the IdentifierGenerator interfaceで、最初にprefix-XX.の形式の既存の主キーから最大の番号を見つけます。
次に、見つかった最大数に1を追加し、prefixプロパティを追加して、新しく生成されたID値を取得します。
このクラスはConfigurableインターフェースも実装しているため、configure()メソッドでprefixプロパティ値を設定できます。
次に、このカスタムジェネレータをエンティティに追加しましょう。 このため、we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:
@Entity
public class Product {
@Id
@GeneratedValue(generator = "prod-generator")
@GenericGenerator(name = "prod-generator",
parameters = @Parameter(name = "prefix", value = "prod"),
strategy = "com.example.hibernate.pojo.generator.MyGenerator")
private String prodId;
// ...
}
また、プレフィックスパラメータを「prod」に設定していることに注意してください。
生成されたID値をより明確に理解するための簡単なJUnitテストを見てみましょう。
@Test
public void whenSaveCustomGeneratedId_thenOk() {
Product product = new Product();
session.save(product);
Product product2 = new Product();
session.save(product2);
assertThat(product2.getProdId()).isEqualTo("prod-2");
}
ここでは、「prod」プレフィックスを使用して生成された最初の値は「prod-1」で、その後に「prod-2」が続きます。
4. 複合識別子
これまで見てきた単純な識別子に加えて、Hibernateでは複合識別子を定義することもできます。
複合IDは、1つ以上の永続属性を持つ主キークラスによって表されます。
主キークラスは、いくつかの条件を満たす必要があります。
-
@EmbeddedIdまたは@IdClassアノテーションを使用して定義する必要があります
-
パブリックでシリアル化可能で、パブリックの引数なしのコンストラクタが必要です
-
equals()およびhashCode()メソッドを実装する必要があります
クラスの属性は、コレクションとOneToOne属性を避けながら、基本、複合、またはManyToOneにすることができます。
4.1. @EmbeddedId
最初に@EmbeddedId,を使用してIDを定義するには、@Embeddable:で注釈が付けられた主キークラスが必要です。
@Embeddable
public class OrderEntryPK implements Serializable {
private long orderId;
private long productId;
// standard constructor, getters, setters
// equals() and hashCode()
}
次に、@EmbeddedIdを使用して、タイプOrderEntryPKのIDをエンティティに追加できます。
@Entity
public class OrderEntry {
@EmbeddedId
private OrderEntryPK entryId;
// ...
}
このタイプの複合IDを使用して、エンティティの主キーを設定する方法を見てみましょう。
@Test
public void whenSaveCompositeIdEntity_thenOk() {
OrderEntryPK entryPK = new OrderEntryPK();
entryPK.setOrderId(1L);
entryPK.setProductId(30L);
OrderEntry entry = new OrderEntry();
entry.setEntryId(entryPK);
session.save(entry);
assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
}
ここで、OrderEntryオブジェクトには、orderIdとproductId.の2つの属性で構成されるOrderEntryPKプライマリIDがあります。
4.2. @IdClass
@IdClassアノテーションは、属性がそれぞれに@Idを使用してメインエンティティクラスで定義されることを除いて、@EmbeddedId,に似ています。
主キークラスは以前と同じように見えます。
OrderEntryの例を@IdClass:で書き直してみましょう
@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
@Id
private long orderId;
@Id
private long productId;
// ...
}
次に、id値をOrderEntryオブジェクトに直接設定できます。
@Test
public void whenSaveIdClassEntity_thenOk() {
OrderEntry entry = new OrderEntry();
entry.setOrderId(1L);
entry.setProductId(30L);
session.save(entry);
assertThat(entry.getOrderId()).isEqualTo(1L);
}
両方のタイプの複合IDの場合、主キークラスに@ManyToOne属性を含めることもできることに注意してください。
Hibernateでは、@Idアノテーションと組み合わせた@ManyToOneアソシエーションで構成される主キーを定義することもできます。 この場合、エンティティクラスはプライマリキークラスの条件も満たす必要があります。
この方法の欠点は、エンティティオブジェクトと識別子の間に分離がないことです。
5. 派生識別子
派生識別子は、@MapsIdアノテーションを使用してエンティティの関連付けから取得されます。
まず、Userエンティティとの1対1の関連付けからIDを取得するUserProfileエンティティを作成しましょう。
@Entity
public class UserProfile {
@Id
private long profileId;
@OneToOne
@MapsId
private User user;
// ...
}
次に、UserProfileインスタンスが関連するUserインスタンスと同じIDを持っていることを確認しましょう。
@Test
public void whenSaveDerivedIdEntity_thenOk() {
User user = new User();
session.save(user);
UserProfile profile = new UserProfile();
profile.setUser(user);
session.save(profile);
assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
}
6. 結論
この記事では、Hibernateで識別子を定義する方法をいくつか見てきました。
例の完全なソースコードはover on GitHubにあります。