循環参照するEntity
ここでいう循環参照とは
互いを外部キーで参照しあう、1組のリソースの関係。
外部キーとなる項目はNotNull制約が付けられているものとする。
JPA上で、1対多・1対1の関連の設定が2つ以上Entity内含む。
チャネルのアカウントIDは通知先のアカウントID、通知先の代表チャネルNoは、チャネルのチャネルNoを外部キーとして参照している
記述例
【通知先エンティティ】
@id
@Column(name = "account_id")
String accounId
-略-
// account_idで関連する、通知先-チャネルの 1:nの 関係
@OneToMany(mappedBy = "notifyDestination", cascade = CascadeType.ALL)
private List<ChannelEntity> channels;
// representativ_channel_noで関連する、通知先-代表チャネルの 1:1 の関係
@OneToOne
@JoinColumn(name = "representative_channel_no", referencedColumnName = "channel_no")
private ChannelEntity channel;
// 関連付け用メソッドは省略
【チャネルエンティティ】
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "channel_no")
Integer channelNo;
-略-
@ManyToOne
@JoinColumn(name = "account_id")
private NotifyDestinationEntity notiryDestination;
JPAの動作
大前提として、JPAは循環参照をサポートしていない。
JPAのInsert時に値が連動できるのは、連動先のレコードが正常にInsert/Update完了しているときのみ。
互いに項目を参照しあっているため、片方のみInsertが完了することはない
こういった場合、JPAは一度参照項目をNullで挿入し、後からUpdateを行うという戦略をとる
今回の定義では、ここでNull制約に抵触する。
対応方針
大原則:循環参照するリソースは設計しない※
別途管理用のフラグを設ける、相互参照を管理するリソースを追加する等
java上で外部キーのどちらかを、手動で設定するような記述に変える → 後述
NativeQueryを使用する。
nullable制約を外す(下策。)
Caution
※あくまで、JPAを利用するのであればという前提での話である。
java上で外部キーのどちらかを、手動で設定するような記述に変える
【通知先エンティティ】
@id
@Column(name = "account_id")
String accounId
-略-
@OneToMany(mappedBy = "notifyDestination", cascade = CascadeType.ALL)
private List<ChannelEntity> channels;
@OneToOne
@JoinColumn(name = "representative_channel_no", referencedColumnName = "channel_no")
【チャネルエンティティ】
@id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "channel_no")
Integer channelNo;
// 外部キーの設定とは別に、手動設定用のカラムを用意する
@Column(name = "account_id")
String accountId;
-略-
// insertable/updatable = false とし、外部キー設定のaccount_idをSQLの登録/更新対象から外す
@ManyToOne
@JoinColumn(name = "account_id", insertable = false, updatable = false) //
private NotifyDestinationEntity notiryDestination;
上記例では、account_idを、双方のエンティティで手動入力の値とすることで、
ChannelEntity側だけ単体でInsert可能な状態にしている。
上記処理を利用するAPIの入力として、account_idが与えられるため行えた対応。
外部キーが双方ともに自動採番のサロゲートキーのようなものである場合、
片方のみ先に採番結果を取得→手動設定という流れをとれば、同様に実装可能だが、
その場合、シーケンスジェネレーターは使用不可となるため注意。
Note
互いのサロゲートキーを外部キーとして参照しあうような設計は避けるべきである。