🍉

JPAの@OneToOneとは

2022/04/22に公開

@OneToOne(一対一)

  • 一対一関係はその反対も一対一である。
  • 1:1なのでFKにデータベースUNIQUE制約を追加する必要がある
  • つまり注テーブルと対象テーブルの内どちらでもFKを置くことができる
  • 二通り考えておく必要がある
    1. 注テーブルにFKを置いた場合
    2. 対象テーブルにFKを置いた場合

1. 注テーブルにFKを置いた場合

片方向

  • ここで注テーブル(FKを持っている)はMemberである
  • JpaMainは無視してください。

Member.java
@Entity
@Getter @Setter
@NoArgsConstructor
@Table(name = "member", uniqueConstraints = {@UniqueConstraint(name = "member_locker", columnNames = {"locker_id"})}) // FKにUNIQUE制約追加
public class Member {
    // ...
    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}
Locker.java
@Entity
@Setter @Getter
@Table(name = "locker") // springbootを使う場合小文字に自動変換されるのでname指定無要
public class Locker {
    @Id
    @Column(name = "locker_id")
    private Long id;
    private String name;
}
  • DDL自動生成機能を使っても基本@OneToOneアノテーションだけではUNIQUE制約を自動的に追加してくれない。@TableのUNIQUE制約オプションを使うかデータベースに直接指定する必要がある。
@Table(name = "member", // テーブルのname指定
	uniqueConstraints = {
		@UniqueConstraint(
			name = "member_locker", // UNIQUEキーのname指定
			columnNames = {"locker_id"} // UNIQUE制約を追加するカラム名
	)})
  • もちろん、読みやすさの観点からデータベースにもオブジェクトにも併記して置いた方がいい
    • テータベースを一々確認しなくてもオブジェクトを見るだけで把握できる。
  • 一対一は多対一(@ManyToOne)と類似している。

双方向

  • 多対一双方向みたいにFKがある所がリレーションのオーナー
  • 反対側にはmappedByを適用

Locker.java
@Entity
@Table(name = "locker")
public class Locker {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "locker_id")
    private Long id;
    private String name;

    @OneToOne(mappedBy = "locker")
    private Member member;
}
Member.java
@Entity
@Table(name = "member", uniqueConstraints = {@UniqueConstraint(name = "member_locker", columnNames = {"locker_id"})}) // FKにUNIQUE制約追加
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;
    private String username;

    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;
}

注テーブルにFKを置いた場合のまとめ

  • 主オブジェクトが対象オブジェクトの参照を持つみたいに主テーブルにFKを置いて対象テーブルを探す。
  • JPAマッピング管理が楽のためOOP開発者が選り好みする。
  • 長所: 主テーブルを照会するだけで対象テーブルにデータがあるかどうか確認できる
  • 短所: 値がないとFKにnullを許さなければ成らない

2. 対象テーブルにFKを置いた場合

片方向(提供しない)

* JPAが提供しないマッピング

双方向

  • 1:1主テーブルにFK双方向とマッピングする方法は一緒。
Locker.java
@Entity
@Table(name = "locker", uniqueConstraints = {@UniqueConstraint(name = "locker_member", columnNames = {"member_id"})}) // FKにUNIQUE制約追加
public class Locker {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "locker_id")
    private Long id;
    private String name;

    @OneToOne
    @JoinColumn(name = "member_id")
    private Member member;
}
Member.java
@Entity
@Table(name = "member")
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;
    private String username;

    @OneToOne(mappedBy = "member")
    private Locker locker;
}

対象テーブルにFKまとめ

  • 対象テーブルにFKが存在
  • 伝統的なDBAが選り好みする
    • 今はMemberが一つしかLockerを持たないが後で政策が変わり沢山持たれることになると、DBAとしてはUNI制約だけ消せば済む。
  • 長所:主テーブルと対象テーブルの関係を1:1から一対多に関係を変更してもテーブルの構造を維持することが出来る。
  • 短所:Proxy機能の限界でLazy Loadingに設定しても常にEager Loadingされる。
    • Proxyに関しては後で説明します。

次(@ManyToMany)

https://zenn.dev/dev_yoon/articles/956cbf4606d76b

Discussion