Hibernate/JPAの識別子の概要
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. 生成された識別子
主キー値を自動的に生成する場合は、@GeneratedValueアノテーションを追加できます。
これは、AUTO、IDENTITY、SEQUENCE、およびTABLEの4つの生成タイプを使用できます。
値を明示的に指定しない場合、生成タイプはデフォルトでAUTOになります。
3.1. AUTO生成
デフォルトの生成タイプを使用している場合、永続性プロバイダーは主キー属性のタイプに基づいて値を決定します。 このタイプは、数値またはUUIDにすることができます。
数値の場合、生成はシーケンスまたはテーブルジェネレーターに基づいていますが、UUID値はUUIDGeneratorを使用します。
まず、自動生成戦略を使用してエンティティの主キーをマッピングしましょう。
@Entity
public class Student {
@Id
@GeneratedValue
private long studentId;
// ...
}
この場合、主キーの値はデータベースレベルで一意になります。
次に、Hibernate5で導入されたUUIDGeneratorを見ていきます。
この機能を使用するには、@GeneratedValueアノテーションを付けたタイプUUIDのIDを宣言する必要があります。
@Entity
public class Course {
@Id
@GeneratedValue
private UUID courseId;
// ...
}
Hibernateは「8dd5f315-9788-4d00-87bb-10eed9eff566」の形式のIDを生成します。
3.2. IDENTITY生成
このタイプの生成は、 IdentityGenerator に依存します。これは、データベースのIdentity列によって生成された値を想定しています。 これは、それらが自動インクリメントされることを意味します。
この生成タイプを使用するには、strategyパラメーターを設定するだけです。
@Entity
public class Student {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private long studentId;
// ...
}
注意すべき点の1つは、IDENTITYの生成によってバッチ更新が無効になることです。
3.3. SEQUENCE生成
シーケンスベースのIDを使用するために、HibernateはSequenceStyleGeneratorクラスを提供します。
データベースがシーケンスをサポートしている場合、このジェネレーターはシーケンスを使用します。 サポートされていない場合は、テーブル生成に切り替わります。
シーケンス名をカスタマイズするために、@GenericGeneratorアノテーションをSequenceStyleGeneratorストラテジーとともに使用できます。
@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ドキュメントで推奨されている生成タイプです。
生成された値はシーケンスごとに一意です。シーケンス名を指定しない場合、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. カスタムジェネレータ
すぐに使用できる戦略を使用したくないとしましょう。 そのために、IdentifierGeneratorインターフェースを実装してカスタムジェネレーターを定義できます。
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");
}
}
この例では、 IdentifierGeneratorインターフェースからgenerate()メソッドをオーバーライドします。
まず、prefix-XXの形式の既存の主キーから最大の番号を見つけます。 次に、見つかった最大数に1を加算し、 prefix プロパティを追加して、新しく生成されたid値を取得します。
このクラスはConfigurableインターフェイスも実装しているため、 configure()メソッドでprefixプロパティ値を設定できます。
次に、このカスタムジェネレーターをエンティティに追加しましょう。
このために、ジェネレータークラスの完全なクラス名を含む戦略パラメーターで@GenericGeneratorアノテーションを使用できます。
@Entity
public class Product {
@Id
@GeneratedValue(generator = "prod-generator")
@GenericGenerator(name = "prod-generator",
parameters = @Parameter(name = "prefix", value = "prod"),
strategy = "com.baeldung.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 アノテーションは、@EmbeddedIdに似ています。 @IdClass との違いは、属性がそれぞれに@Idを使用してメインエンティティクラスで定義されることです。 主キークラスは以前と同じように見えます。
OrderEntryの例を@IdClassで書き直してみましょう。
@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
@Id
private long orderId;
@Id
private long productId;
// ...
}
次に、OrderEntryオブジェクトに直接id値を設定できます。
@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では、@ManyToOneアソシエーションと@Idアノテーションを組み合わせた主キーを定義することもできます。 この場合、エンティティクラスは主キークラスの条件を満たす必要があります。
ただし、このメソッドの欠点は、エンティティオブジェクトと識別子の間に分離がないことです。
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で識別子を定義する複数の方法を見てきました。
例の完全なソースコードは、GitHubのにあります。