【iDempiere Lab】テナントの初期化と削除プロセスに外部キー制約の整合性をチェックする処理の実装(v12)

2026年4月23日に【JPIERE-0158】クライアント(テナント)の初期化と削除プロセスの処理の最後に、外部キー制約の整合性をチェックする処理を追加しました。このコンテンツでは、この処理を実装した背景や、どのように活用してほしいと思っているのか等を説明します。

このコンテンツと合わせて下記のコンテンツも参考に【JPIERE-0158】クライアント(テナント)の初期化と削除を計画的に活用してもらえれば幸いです。

実装の背景(実装した理由)

PostgreSQLは外部キー制約の整合性を担保してくれない

「テナントの初期化と削除プロセス」においてはトリガーを使って外部キー制約のチェックを無効にしてから、レコードを削除して、最後に有効に戻しています。

しかしながら有効に戻すタイミングでは、PostgreSQLは外部キー制約の整合性をチェックしてくれません。そのため不整合なレコードが存在してしまう可能性があります。

このコンテンツで記述しているPostgreSQLの挙動の多くは、トリガーで外部キー制約のチェックが無効になっている状況での挙動について言及しています。普通に使っている状況での挙動とは異なることもあるかと思いますのでご留意ください。

※検証に使用しているPostgreSQLのバージョンは16です。

【補足説明】外部キー制約のチェックを有効のまま実行したい場合

2024年5月17日(ver11)に行った更新で、外部キー制約のチェックを無効にせずに(有効のまま)「テナントの初期化と削除プロセス」を実行できるようにしています。

システムコンフィグ設定で「JP_EXECUTE_UPDATE_CONSTRAINT」に"N"をセットすると、外部キー制約の無効化と再有効化の処理をスキップします。

 

PostgreSQLが外部キー制約の整合性をチェックするタイミング

◆整合性のチェックのタイミングはコミット時

iDempiere/JPiereの多くの外部キー制約は「DEFERRABLE INITIALLY DEFERRED」となっており、PostgreSQLはレコードの更新や削除のタイミングではなく、基本的にはコミットのタイミングで外部キー制約の整合性のチェックを行うようです。

 

◆外部キー制約のチェックを無効にしても整合性のチェックをする場合がある

「テナントの初期化と削除プロセス」においてはトリガーを使って外部キー制約のチェックを無効にしてから、レコードを削除して、途中で何度かコミットを行い、最後に有効に戻しています。

外部キー制約のチェックを無効にしていても、PostgreSQLではコミットしたタイミングで整合性のチェックを行い、不整合なレコードが発見されて場合にはエラーにして「テナントの初期化と削除プロセス」が強制終了される場合があります。

外部キー制約のチェックを無効にしていてもコミットするタイミングでチェックされることがあるので、2026年4月23日の改修では整合性のチェックをする処理の追加といっしょにコミットするタイミングも少し見直しています。

 

◆整合性のチェックを必ずするわけではない

「テナントの初期化と削除プロセス」においては外部キー制約のチェックを無効にしていても、コミットするタイミングでPostgreSQLの整合性のチェックが行われることがあるのですが、これは言い換えると、コミットするタイミングでPostgreSQLは必ず整合性のチェックをするわけではないということです。

PostgreSQLが「外部キーに関係する操作が一度も行われなかった」と判断したケースにおいては、チェックが行われない場合があるようです。

そのためPostgrSQLに外部キー制約の整合性チェックをすべて任せているとチェック対象外と判断されて不整合なレコードが出来てしまう可能性があるので「テナントの初期化と削除プロセス」の処理の最後に整合性のチェックを実装しました。

テナントの初期化と削除プロセス
テナントの初期化と削除プロセス

2回行う外部キー制約の整合性チェック

「テナントの初期化と削除プロセス」の処理の最後に追加した整合性のチェック処理は、外部キー制約のチェックを有効に戻す処理の直前と直後の2回行います。

システムコンフィグ設定で「JP_EXECUTE_UPDATE_CONSTRAINT」に"N"をセットして外部キー制約のチェックを無効にしていない場合でも、ここで説明する整合性のチェックは行われます。

1回目の整合性チェックは不整合のレコードを特定し、可能であれば整合性を確保するため

1回目の外部キー制約の整合性チェックは、トリガーを使って外部キー制約のチェックを有効に戻す直前に行われます。1回目のチェックの目的は不整合があるレコードを特定し(ログに記録)、可能であれば整合性を確保します。

◆不整合があるレコードを特定しログに記録する

不整合があるレコードが発見された場合にはログに記録します。ログは「テナントの初期化と削除プロセス」実行後に表示されるレポートで確認できますが、「テナントの初期化と削除のログ」ウィンドウでも確認できます。

不整合が発見された時のログ
不整合が発見された時のログ

説明欄フィールドに"警告"を先頭文字として、対象のレコードが特定できるような情報を記録しています。

【説明欄フィールドの例】

警告 : Inconsistency in FK Constraint: Record ID = 1000065 -> CONSTRAINT NAME: adwfresponsible_adwfnode - TABLE: ad_wf_node - COLUMN: ad_wf_responsible_id

SQL文フィールドには、整合性を確認するのに使用したSQLを記録しています。

【SQL文フィールドの例】

SELECT * FROM adempiere.ad_wf_node WHERE ad_wf_responsible_id IS NOT NULL AND ad_wf_responsible_id NOT IN (SELECT ad_wf_responsible_id FROM adempiere.ad_wf_responsible);

 

◆可能であれば整合性を確保する

不整合があるレコードが発見された場合、不整合なデータが格納されているカラムの設定で、外部参照キー制約タイプが”Set Null”、"Cascade"、"Model Cascade"のいずれかが設定されていれば、整合性を確保する処理が行われます。

テーブルとカラム
テーブルとカラム
  • Set Null … 不整合が発見されたレコードの対象カラムのデータをnullにします。
  • Cascade … 不整合が発見されたレコードを削除します。
  • Model Cascade … 不整合が発見されたレコードを削除します。

不整合が修正できた場合には、その旨もログに記録されます。

【"Set Null"で整合性を確保したログ】

Set Nullで整合性を確保したログ
Set Nullで整合性を確保したログ

説明欄フィールドに"OK (Automatic Repair)"の文字を先頭に不整合を修正したレコードを特定できる情報が記録されます。そしてSQL文フィールドには修正するのに使用したSQL文が記録されます。

 

【"Cascade"と"Model Cascade"で整合性を確保したログ】

"Cascade"と"Model Cascade"で整合性を確保したログ
"Cascade"と"Model Cascade"で整合性を確保したログ

【ポイント】CascadeとModel Cascadeでは、削除処理によって新たな不整合が発生する可能性があることを意識して下さい!!

この整合性を確保する処理は、外部キー制約のチェックを無効にしている状態で整合性を確保する処理を行うため、Cascade と Model Cascade の整合性を確保する過程で 新たな不整合が発生する可能性があります。

不整合となっているレコードを削除する場合、まずそのレコードを参照している子レコード(以下「不整合の子レコード」)に対して整合性を確保するための前処理を行います。

前処理の内容は次のとおりです。

◆不整合の子レコードの外部キーが 必須入力(NOT NULL) の場合

 → 不整合の子レコードを削除する

◆不整合の子レコードの外部キーが 任意入力(NULL 可) の場合

 → 外部キーに NULL をセットする

不整合の子レコードが削除された場合、その不整合の子レコードを参照している別のレコードが存在する可能性があり、そこで 新たな不整合が発生する可能性があります

この点を理解しておくことで、なぜ不整合なレコードが残るのか、そしてどのように対応すべきかを判断しやすくなるのではないかと思います。

 

【不整合の子レコードを削除した時のログ】

「不整合の子レコード」を削除した時のログ
「不整合の子レコード」を削除した時のログ

「不整合の子レコード」を削除した場合、下記のイメージの赤線内のように不整合のレコードがある"警告"のログから、「不整合の子レコード」を削除したログ、そして、最後に不整合のレコードを削除して整合性を確保した"OK (Automatic Repair)"の一連のログが表示されます。

ログの一覧表示
ログの一覧表示

 

【技術情報】整合性を確保する処理の対象外になる場合

外部参照キー制約タイプに”Set Null”、"Cascade"、"Model Cascade"のいずれかが設定されてあっても主キー[テーブル名+_IDカラム]が無いテーブルは整合性を確保する処理の対象外になります。

 

2回目の整合性チェックは最終確認

2回目の外部キー制約の整合性チェックは、トリガーを使って外部キー制約のチェックを有効に戻しコミットされた直後に行われます。2回目のチェックの目的は最終確認です。1回目の整合性チェックで不整合なレコードが発見されても修正できている場合がありますので、最後にもう一度確認しています。

◆不整合なレコードが発見された場合には、外部キー制約単位で件数表示

2回目のチェックで不整合なレコードが残っていた場合、その制約名とその制約が設定されているテーブルとカラムが特定できる情報とともに不整合になっているレコードの件数がログに記録されています。

プロセス実行後に表示される画面でも不整合がある場合には確認できるようにしていますので【要確認】と表示されていないかどうか意識してみるようにして下さい。表示されていない場合は外部キー制約に不整合は発見されなかったことになります。

プロセス実行後の画面
プロセス実行後の画面

 

◆全件整合性チェックを行ったログも取得可能

整合性のチェックを行ったログも記録することができます。「テナントの初期化と削除プロセス」の「更新が0件のSQLのログを記録する」をONにして実行してみてください。

テナントの初期化と削除プロセス
テナントの初期化と削除プロセス

ログとして説明欄フィールドに"OK (No Inconsistency)"を先頭に、整合性を確認した外部キー制約を特定できる情報が記録されています。SQL文フィールドには整合性を確認するのに使用したSQL文が記録されます。

単票表示
単票表示
一覧表示
一覧表示

活用法

「テナントの初期化と削除プロセス」は、削除プロファイルを作成し事前に何度もテストした上で本番実行することを想定しています。

処理の途中で外部キー制約のエラーでプロセスが強制終了となるケースおいては、PostgreSQLが整合性のチェックをしてくれていることによるエラーですので、エラーにならにように削除プロファイルの構成を見直したり、カラムの設定で外部参照キー制約タイプを設定するなりして対応して下さい。

PostgreSQLにより外部キー制約の不整合が発見されて強制終了となった場合には、上記の赤線で囲んでいるところでエラーになっている情報が確認できます。

【エラー表示例】

org.adempiere.exceptions.AdempiereException: Record not deleted - there are dependent records in the table WFノード for the column WF責任者 -> org.postgresql.util.PSQLException: ERROR: update or delete on table "ad_wf_responsible" violates foreign key constraint "adwfresponsible_adwfnode" on table "ad_wf_node" 詳細: Key (ad_wf_responsible_id)=(1000003) is still referenced from table "ad_wf_node".

 上記のエラー表示例では、WFノード[AD_WF_Node]テーブルのWF責任者[AD_WF_Responsible_ID]カラムに設定されている外部キー制約"adwfresponsible_adwfnode"に、不整合があることが確認できます。

不整合な状態になっているテーブルとカラムを特定したら、外部参照キー制約タイプに”Set Null”、"Cascade"、"Model Cascade"のいずれかを設定して、カラム同期を実行して下さい。

基本的には、この作業を「テナントの初期化と削除プロセス」が正常終了するまで繰り返します。しかしながら正常終了しても、外部キー制約に違反している不整合なデータ残っている可能性があります。

ログを確認し不整合なデータが残っていた場合には、ログからテーブルとカラムを特定し、同様の作業を行います。

 

【ポイント】外部参照キー制約タイプを変更したらキャッシュリセットする

外部参照キー制約タイプを変更してカラム同期を行ったらキャッシュリセットしてから、「テナントの初期化と削除プロセス」を実行するようにして下さい。

 

【ポイント】一括更新プロセスを活用して事前にデータを整える

【JPIERE-0621】一括更新プロセスでは、削除プロファイルにより更新内容を事前に定義して記録しておけるので、繰り返し何度でも同じデータ更新処理を行うことができます。

「テナントの初期化と削除プロセス」の前に事前処理として、【JPIERE-0621】一括更新プロセスを活用して、意図した通り削除できるようにデータを整えておくことも有効な対応方法になると考えています。

参考情報