JPAでの1対1の関係
1. 概要
このチュートリアルでは、JPAで1対1のマッピングを作成するさまざまな方法を見ていきます。
Hibernateフレームワークの基本を理解する必要があるため、追加の背景については、ガイドとSpringを使用したHibernate5のガイドを確認してください。
2. 説明
ユーザー管理システムを構築していて、上司からユーザーごとに住所を保存するように依頼されたとします。 ユーザーには1つの郵送先住所があり、郵送先住所には1人のユーザーしか関連付けられていません。
これは、ユーザーとアドレスエンティティ間の1対1の関係の例です。
次のセクションでこれを実装する方法を見てみましょう。
3. 外部キーの使用
3.1. 外部キーを使用したモデリング
次のER図を見てみましょう。これは、外部キーベースの1対1のマッピングを表しています。
この例では、usersのaddress_id列は、外部キーからaddressです。
3.2. JPAでの外部キーを使用した実装
まず、 User クラスを作成し、適切に注釈を付けましょう。
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
// ... getters and setters
}
関連するエンティティフィールドAddressに@OneToOneアノテーションを配置することに注意してください。
また、 @JoinColumnアノテーションを配置して、addressテーブルの主キーにマップするusersテーブルの列の名前を構成する必要があります。 名前を指定しない場合、Hibernateはいくつかのルールに従ってデフォルトのルールを選択します。
最後に、次のエンティティでは、@JoinColumnアノテーションを使用しないことに注意してください。 これは、外部キー関係の所有側でのみ必要になるためです。 簡単に言えば、外部キー列を所有している人は誰でも@JoinColumnアノテーションを取得します。
アドレスエンティティは少し単純になります。
@Entity
@Table(name = "address")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(mappedBy = "address")
private User user;
//... getters and setters
}
また、ここにも@OneToOneアノテーションを配置する必要があります。 これは双方向の関係だからです。
4. 共有主キーの使用
4.1. 共有主キーを使用したモデリング
この戦略では、新しい列 address_id を作成する代わりに、アドレステーブルの主キー列(user_id)をユーザーテーブルの外部キーとしてマークします。
これらのエンティティは1対1の関係にあるという事実を利用して、ストレージスペースを最適化しました。
4.2. JPAでの共有主キーを使用した実装
定義がわずかに変更されていることに注意してください。
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Address address;
//... getters and setters
}
@Entity
@Table(name = "address")
public class Address {
@Id
@Column(name = "user_id")
private Long id;
//...
@OneToOne
@MapsId
@JoinColumn(name = "user_id")
private User user;
//... getters and setters
}
mappedBy 属性は、アドレステーブルに外部キーが存在するため、ユーザークラスに移動されました。 また、 @PrimaryKeyJoinColumnアノテーションを追加しました。これは、Userエンティティの主キーが関連するAddressエンティティの外部キー値として使用されることを示します。
Addressクラスで@Idフィールドを定義する必要があります。 ただし、これは user_id 列を参照し、@GeneratedValueアノテーションを使用しなくなったことに注意してください。 また、 User を参照するフィールドに、 @MapsIdアノテーションを追加しました。これは、主キー値がUserからコピーされることを示します。実在物。
5. 結合テーブルの使用
1対1のマッピングには、オプションと必須の2つのタイプがあります。これまで、必須の関係のみを確認してきました。
ここで、従業員がワークステーションに関連付けられると想像してみましょう。 これは1対1ですが、従業員がワークステーションを持っていない場合や、その逆の場合があります。
5.1. 結合テーブルを使用したモデリング
これまで説明してきた戦略では、オプションの関係を処理するために、列にnull値を入力する必要があります。
通常、結合テーブルを検討するときは、多対多の関係を考えますが、この場合に結合テーブルを使用すると、これらのnull値を排除するのに役立ちます。
これで、関係があるときはいつでも、 emp_workstation テーブルにエントリを作成し、nullを完全に回避します。
5.2. JPAでの結合テーブルを使用した実装
最初の例では、@JoinColumnを使用しました。 今回は、@JoinTableを使用します。
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name = "emp_workstation",
joinColumns =
{ @JoinColumn(name = "employee_id", referencedColumnName = "id") },
inverseJoinColumns =
{ @JoinColumn(name = "workstation_id", referencedColumnName = "id") })
private WorkStation workStation;
//... getters and setters
}
@Entity
@Table(name = "workstation")
public class WorkStation {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
//...
@OneToOne(mappedBy = "workStation")
private Employee employee;
//... getters and setters
}
@JoinTable は、関係を維持しながらテーブル結合戦略を採用するようにHibernateに指示します。
また、従業員は、結合テーブルアノテーションを使用することを選択したため、この関係の所有者です。
6. 結論
この記事では、JPAとHibernateで1対1の関連付けを維持するさまざまな方法と、それぞれをいつ使用するかを学びました。
この記事のソースコードは、GitHubのにあります。