spring-data-jpaを使った場合の単方向と双方向について
spring-data-jpaを使った場合の単方向と双方向についてよく分からなかったので、自分なりに調べたことなどをまとめてみました
単方向
- 後から双方向にデータをたどりたいといった時に単方向で作ってしまうと改修に手間が掛かる可能性がある
one側
@Entity @Data public class Department implements Serializable { @Id @Column(name = "department_id") private String departmentId; @OneToMany(name ="member_fk", referencedColumnName="member_id", nullable = false) private List<Member> members; }
many側
@Entity @Data public class Member implements Serializable { @Id @Column(name = "member_id") private String memberId; }
双方向
双方向には下記の2パターンが存在する
- mappedByを使う方法
- @JoinTableを利用する方法
- あいだにテーブルを仲介する場合に利用
mappedByを使う方法
- @OneToManyアノテーションを付けて、mappedByで関連させる相手側エンティティのフィールドを指定する
- 相手側(多側)で外部キー用のフィールドを持つ必要がある
one側
@Entity @Data public class Department implements Serializable { @Id @Column(name = "department_id") private String departmentId; @OneToMany(mappedBy="departmentFk") private List<Member> members; }
many側
@Entity @Data public class Member implements Serializable { @Id @Column(name = "member_id") private String memberId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "department_fk", referencedColumnName = "departmentId") private Department departmentFk; }
JoinColumnアノテーションについて
- 結合表のための外部キーカラム名を「name」で指定できる
- 外部キーカラムによって参照された結合先テーブルのカラム名を「referencedColumnName」で指定できる
- ※その場合は必ず結合先テーブルのプライマリキーとなっているカラムを指定
- 複数の外部キーカラムがある場合は@JoinColumnsを使用して、それぞれの関連に対して@JoinColumnを指定する必要がある
- @JoinColumnを明示的に指定しない場合は、リレーションが双方向でなく単方向に扱われてしまう
@JoinTableを利用する方法
※こちらは概要のみ
- @JoinTableを使ってテーブル結合させ、joinColumnsと@JoinColumnを使って結合表のための外部キーカラム名を「name」で指定する
- また、外部キーカラムによって参照された結合先テーブルのカラム名を「referencedColumnName」で指定する
- 同時にinverseJoinColumnsと@JoinColumnを使って結合表のための相手側の外部キーカラム名を「name」で指定できる
- それと合わせて、外部キーカラムによって相手に参照される結合先テーブルのカラム名を「referencedColumnName」で指定できる
単方向を使うべきか双方向を使うべきか?(※編集中)
単方向OneToManyをお薦めしない理由
子テーブルは対応する子オブジェクトにマップされないカラム(FK)を持つことになるから一般的じゃないってのが主な理由 もし単方向関連だったら、子オブジェクトは親オブジェクト無しでも存在できてしまう。もし親が必要だとしたら双方向ににするべきでは!?
ちょっと視点を変えて「関連の所有」という見方をすると・・(※ここは参考とだいぶ違うので理解が間違っているかも・・) 上記の例の場合だと、一対多の関連はどちらが所有してるのか? → 直感的に考えるとdepartmentが複数のmemberを所有しているため「department」では? → 実際は「member」、なぜならmemberは外部キーから関連するdepartmenetが辿れるが、departmentからは対応するmemberを辿れないため → こうなってくると当初考えていたのと所有者が逆に・・ => ORマッパー的には逆のように見えてしまうため単方向を使うのは避けたほうがよい??
FetchTypeについて
データをDBから取得する際に関連テーブルの方も一緒に取り出すかどうかで下記の2タイプがある
- LAZY → 取得しない(※正確には遅延ロード)
- EAGER → 取得する
LAZY
- 関連テーブルへのアクセスが発生したタイミングでロードされる
- 元テーブルのみのアクセスの場合は関連テーブルのロードは行われない
- フィールドの呼び出しを最初の呼び出しで行う
- 元テーブルのみのアクセスの場合は関連テーブルのロードは行われない
EAGER
- 元テーブルにアクセスした際に関連テーブルも合わせてロードしデータが取得・格納される
- そのフィールドにアクセスがあった時点でフィールドをデータベースから呼び出す
最後に
単方向・双方向のどちらを使うか、また使った際に各オプションをどうセットするべきかについてちゃんと理解しする必要があったため今回、まとめてみた
個人的には当初は外部キーはEntityクラスとして持たないほうが良いのでは?と思い単方向を使うつもりでいたが、関連についての記事などを見てみたりあらためて調べると双方向にシフトした方が、 本来の使い方や、今後の拡張性も考えると良さそうな気がしてきた。
ただし、この辺りはN+1問題や今回はあまり取り扱えていない「insertable,updateble,nullable」の辺りもしっかり理解した上で判断するのが良さそうなのでそのあたりについても調べてまとめる必要がありそう