###################################### 循環参照するEntity ###################################### ここでいう循環参照とは ============================ - 互いを外部キーで参照しあう、1組のリソースの関係。 - 外部キーとなる項目はNotNull制約が付けられているものとする。 - JPA上で、1対多・1対1の関連の設定が2つ以上Entity内含む。 .. image:: ./Images/circular_ref.png - チャネルのアカウントIDは通知先のアカウントID、通知先の代表チャネルNoは、チャネルのチャネルNoを外部キーとして参照している 記述例 ============================ :: 【通知先エンティティ】 @id @Column(name = "account_id") String accounId -略- // account_idで関連する、通知先-チャネルの 1:nの 関係 @OneToMany(mappedBy = "notifyDestination", cascade = CascadeType.ALL) private List 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 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:: | 互いのサロゲートキーを外部キーとして参照しあうような設計は避けるべきである。