1. 概要

このチュートリアルでは、JPAで1対1のマッピングを作成するさまざまな方法を見ていきます。

Hibernateフレームワークの基本を理解する必要があるため、追加の背景については、ガイドとSpringを使用したHibernate5のガイドを確認してください。

2. 説明

ユーザー管理システムを構築していて、上司からユーザーごとに住所を保存するように依頼されたとします。 ユーザーには1つの郵送先住所があり、郵送先住所には1人のユーザーしか関連付けられていません。

これは、ユーザーアドレスエンティティ間の1対1の関係の例です。

次のセクションでこれを実装する方法を見てみましょう。

3. 外部キーの使用

3.1. 外部キーを使用したモデリング

次のER図を見てみましょう。これは、外部キーベースの1対1のマッピングを表しています。

この例では、usersaddress_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にあります。