Oracle APEXは自前の楽観的同時実行制御(Optimistic Concurrency Control)の仕組みを持っています。
JSON二面性ビューおよび
JSONコレクション表はそれぞれの行、つまりJSONオブジェクトにメタデータとしてETAGを含みます。そのため、APEXのデータ・ソースとしてJSON二面性ビューまたはJSONコレクション表を指定すると、同時実行制御が二重になる可能性があります。
JSON二面性ビューでは、更新するJSONオブジェクトのETAGと表に保存されているJSONオブジェクトのETAGが一致しているときに更新が実行されます。一致していないと、ORA-42699が発生します。
JSONコレクション表はJSON二面性ビューとは異なり、JSONオブジェクトにETAGが含まれますが、楽観的同時実行制御は実装されていません。開発者がETAGを使って、同時実行制御を実装する必要があります。
Oracle APEXのデータ・ソースとして二面性ビューおよびJSONコレクション表を指定したときに、同時実行制御がどのように動作するか確認してみます。
最初にJSON二面性ビューEMPDEPT_DVを作成します。サンプル・データセットのEMP/DEPTに含まれる表EMPとDEPTを元にしています。
作成された二面性ビューEMPDEPT_DVを検索し、内容を確認します。
select data from empdept_dv;
すべてのオブジェクトに"_metadata": { "etag": "....", "asof": "...." }が含まれています。
JSONコレクション表EMPDEPT_Jを作成します。WITH ETAGを指定します。
create json collection table empdept_j with etag;
二面性ビューEMPDEPT_DVの内容をJSONコレクション表EMPDEPT_Jにコピーします。二面性ビューEMPDEPT_DVに含まれるメタデータ_metadataは、コピーするJSONオブジェクトから除きます。
insert into empdept_j select json_transform(d.data, remove '$._metadata') data from empdept_dv d;
JSONコレクション表EMPDEPT_Jを検索し、内容を確認します。
select data from empdept_j;
JSONコレクション表EMPDEPT_Jを作成する際にWITH ETAGを指定しているため、それぞれのJSONオブジェクトにETAGが含まれています。JSONコレクション表のETAGは行のチェックサムではなく行バージョンが生成されているため、異なる行で同じETAGが割り当たることもあります。JSONコレクション表には属性asofはありません。
以上でデータベース側の準備ができました。
コマンドラインでの楽観的同時実行制御の確認
コマンドラインから、同時実行制御の動作を確認します。JSON二面性ビュー、JSONコレクション表の両方とも検索対象の列はDATAのみなのでheadingはoff、楽観的同時実行制御の確認なのでautocommitをonにします。出力に改行が含まれるとうまくコピペができないので、pages、lines、trims、trimoなどのオプションも指定します。
SQL> set heading off
SQL> set autocommit on
SQL> set pages 1000 lines 1000 trims on trimo on
JSON二面性ビュー
従業員番号7788のSCOTTのデータを取り出します。
SQL> select d.data from empdept_dv d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"F8C263E0EF750556C959750077B31838","asof":"000000000129DECB"}}
SQL>
給与を変更したJSONドキュメントに置き換えます。この例では3000を4000に変更しています。データベースに保存されているETAGと更新するJSONドキュメントのETAGが同じ値なので、更新は成功します。
SQL> update empdept_dv d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"F8C263E0EF750556C959750077B31838","asof":"000000000129DECB"}}' where d.data."_id"."empno" = 7788;
1行更新しました。
コミットが完了しました。
SQL>
更新された内容を確認します。_metadataのetagが異なる値に変わっています。
SQL> select d.data from empdept_dv d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"6907379BED6ABD00E3381F8A7E6C5068","asof":"000000000129DF3A"}}
SQL>
再度、更新された内容を確認します。データに変更は無いため_metadataのetagの値は同じですが、asofは検索される度に変わります。
SQL> select d.data from empdept_dv d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"6907379BED6ABD00E3381F8A7E6C5068","asof":"000000000129DFE9"}}
SQL>
最初に検索したJSONオブジェクトで、SCOTTのデータを置き換えます。dataに渡しているETAGと現状のETAGが一致していないため、ORA-42699が発生します。
SQL> update empdept_dv d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"F8C263E0EF750556C959750077B31838","asof":"000000000129DE86"}}' where d.data."_id"."empno" = 7788;
次のコマンドの開始中にエラーが発生しました : 行 1 -
update empdept_dv d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"F8C263E0EF750556C959750077B31838","asof":"000000000129DE86"}}' where d.data."_id"."empno" = 7788
エラー・レポート -
ORA-42699: updateを実行できません(JSONリレーショナル二面性ビュー'EMPDEPT_DV'): データベースのID 'FB03C24E5900'のドキュメントのETAGが、渡されたETAGと一致しませんでした。
https://docs.oracle.com/error-help/db/ora-42699/
More Details :
https://docs.oracle.com/error-help/db/ora-42699/
SQL>
更新するJSONオブジェクトに_metadata(etag含む)が含まれていないと、データが更新されます。
SQL> update empdept_dv d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"}}' where d.data."_id"."empno" = 7788;
1行更新しました。
コミットが完了しました。
SQL>
更新された結果を確認します。ETAGはチェックサムです。最初の検索結果とデータが同じなので、ETAGも同じ値になっています。ASOFは検索ごとに更新されています。
SQL> select d.data from empdept_dv d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"F8C263E0EF750556C959750077B31838","asof":"000000000129E350"}}
SQL>
更新するJSONドキュメントから_metadataを除くと更新されることより、JSON二面性ビューの楽観的ロックは強制されるものではなく、開発者が意識して実装する必要がありそうです。
JSONコレクション表
JSONコレクション表EMPDEPT_Jを対象として、JSON二面性ビューEMPDEPT_DVで実施した操作を行います。
従業員SCOTTの情報を取り出します。JSONコレクション表なので_metadataのasofはありません。
SQL> select d.data from empdept_j d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F19B4FCB1D5A2B9E0630500590AAC77"}}
SQL>
給与を更新します。
SQL> update empdept_j d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F19B4FCB1D5A2B9E0630500590AAC77"}}' where d.data."_id"."empno" = 7788;
1行更新しました。
コミットが完了しました。
SQL>
再度、検索します。salの値およびETAGの値が更新されていることが確認できます。
SQL> select d.data from empdept_j d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18716754E0630500590A1DD3"}}
SQL>
最初に検索したJSONオブジェクトで、SCOTTのデータを置き換えます。ETAGは異なっていますが、そのまま更新されます。
SQL> update empdept_j d set d.data = '{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F19B4FCB1D5A2B9E0630500590AAC77"}}' where d.data."_id"."empno" = 7788;
1行更新しました。
コミットが完了しました。
SQL>
更新結果を確認します。JSONコレクション表のETAGはチェックサムではなく行バージョンであるため、データが同じでも最初の検索結果とETAGは異なります。JSON二面性ビューのETAGとは異なります。
SQL> select d.data from empdept_j d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18736754E0630500590A1DD3"}}
SQL>
JSONコレクション表ではETAGは生成されますが、JSON二面性ビューとは異なり、それ自体では同時実行制御は行わないことがわかります。
JSONコレクション表のETAGを使って同時実行制御を行う例として、以下のプロシージャupdate_jct_with_etagを作成してみました。
先ほどの手順のupdate文の実行をupdate_jct_with_etagの呼び出しに置き換えます。
最初に従業員SCOTTのデータを取得します。
SQL> select d.data from empdept_j d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18736754E0630500590A1DD3"}}
SQL>
SQL> exec update_jct_with_etag('{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18736754E0630500590A1DD3"}}');
PL/SQLプロシージャが正常に完了しました。
コミットが完了しました。
SQL>
再度、検索します。
SQL> select d.data from empdept_j d where d.data."_id"."empno" = 7788;
{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":4000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18766754E0630500590A1DD3"}}
SQL>
最初に検索したJSONオブジェクトで、SCOTTのデータを置き換えます。プロシージャupdate_jct_with_etagは現行のETAGと更新するデータに含まれるETAGを比較して、不一致を検出して例外を上げています。
SQL> exec update_jct_with_etag('{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18736754E0630500590A1DD3"}}');
BEGIN update_jct_with_etag('{"_id":{"empno":7788},"ename":"SCOTT","job":"ANALYST","sal":3000,"mgr":7566,"comm":null,"deptno":20,"dept":{"deptno":20,"dname":"RESEARCH","loc":"DALLAS"},"_metadata":{"etag":"2F2ABCBB18736754E0630500590A1DD3"}}'); END;
*
行でエラーが発生しました 1:
ORA-06510: PL/SQL: ユーザー定義の例外が発生しましたが、処理されませんでした
ORA-06512: "WKSP_APEXDEV.UPDATE_JCT_WITH_ETAG", 行27
ORA-06512: 行1
https://docs.oracle.com/error-help/db/ora-06510/
More Details :
https://docs.oracle.com/error-help/db/ora-06510/
https://docs.oracle.com/error-help/db/ora-06512/
SQL>
JSONコレクション表ではETAGは生成しますが、同時実行制御の実装は開発者に任されています。
二面性ビューEMPDEPT_DVおよびJSONコレクション表EMPDEPT_Jを操作するAPEXアプリケーションを作成します。
作成するアプリケーションの名前はJSON ETAG OCCとします。
アプリケーションが作成されたら、共有コンポーネントのJSONソースを開き、二面性ビューEMPDEPT_DVとJSONコレクション表EMPDEPT_Jを、JSONソースとして作成します。
Oracle APEX 24.2.2では不具合のため、データソースの二面性ビューを作成できません。ワークアラウンドとして、二面性ビューをJSONソースとして作成します。
JSONソースを開いたら、作成をクリックします。
最初に二面性ビュー
EMPDEPT_DVを元にした
JSONソースを作成します。
名前はEMPDEPT_DV、JSONソース・タイプはJSON列のある表を選択します。JSON列のある表としてEMPDEPT_DVを選択します。
次へ進みます。
JSON列1はDATA(Json)になります。
次へ進みます。
C_ID_EMPNOの主キーにチェックを入れます。今回は属性_idにe.empnoの代わりに{ "empno": e.empno }を設定しているため、主キーが自動検出されていません。
C_METADATA_ETAG(セレクタ_metadata.etag)およびC_METADATA_ASOF(セレクタ_metadata.asof)のデータ型がVarchar2として検出されていることを確認します。異なる場合は、作成されたJSONソースのデータ・プロファイルを更新します。
作成をクリックします。
JSONソースとしてEMPDEPT_DVが作成されます。
次にJSONコレクション表EMPDEPT_Jを元にしたJSONソースを作成します。
名前はEMPDEPT_J、JSONソース・タイプはJSONコレクション表を選択します。JSONコレクション表としてEMPDEPT_Jを選択します。
次へ進みます。
EMPDEPT_DVと同様に、C_ID_EMPNOの主キーにチェックを入れます。
C_METADATA_ETAGのデータ型がVarchar2として検知されていることを確認します。JSONコレクション表にはC_METADATA_ASOFはありません。
作成をクリックします。
JSONソースとして
EMPDEPT_DVおよび
EMPDEPT_Jが作成されました。
以上でJSONソースの準備は完了です。
それぞれのデータ・ソースを元にフォーム付き対話モード・レポートのページを作成します。
ページの作成をクリックします。
対話モード・レポートを選択します。
最初に二面性ビューEMPDEPT_DVをデータ・ソースとしたページを作成します。
ページ定義の名前は二面性ビューとし、フォーム・ページを含めるをオンにします。フォーム・ページ名はEMPDEPT_DVとします。
データ・ソースにJSONソースを選び、JSONソースにEMPDEPT_DVを選択します。
次へ進みます。
主キー列1はC_ID_EMPNO (Number)です。
ページの作成を実行します。
同様の手順でJSONコレクション表EMPDEPT_Jをデータ・ソースとしたページを作成します。
ページ定義の名前はJSONコレクション表、フォーム・ページ名はEMPDEPT_J、JSONソースとしてEMPDEPT_Jを指定し、ページを作成します。
二面性ビューEMPDEPT_DVおよびJSONコレクション表EMPDEPT_Jをデータ・ソースとした対話モード・レポートとフォームのページが作成されました。
以上でAPEXアプリケーションの準備は完了です。
APEXアプリケーションでの楽観的同時実行制御の確認
作成したAPEXアプリケーションを使って、従業員の給与を同時に更新してみます。
JSON二面性ビュー
作成したAPEXアプリケーションを実行し、二面性ビューの対話モード・レポートを開きます。
列C Metadata Etag(セレクタ_metadata.etag)の値は列のデータが同じ値であれば、同じ値になります。列C Metadata Asof(セレクタ_metadata.asof)の値は、二面性ビューEMPDEPT_DVを検索した時点の値になります。結果として、レポートがリフレッシュされるたびに変更されます。
従業員
SCOTTのフォームを開きます。このとき、レポート上の列C Metadata Asofの値を覚えておきます。

従業員SCOTTの編集フォームが開きます。
ページ・アイテムのC Metadata Asofの値は、レポートに表示されている値から変わっています。
元々、レポートからフォームが開かれるときは、主キーの値のみがフォームに渡されます。フォームのページに作成されている
初期化プロセスにより、渡された主キーの値からデータ・ソースを検索し、その時点での値がページ・アイテムに設定されます。
対話モード・レポートでは、属性のターゲットで、フォームに渡される値が設定されています。
ターゲットのアイテムの設定に、ページ遷移時に渡される値が設定されています。レポート列C_ID_EMPNOの値がフォームのページ・アイテムP3_C_ID_EMPNOに渡されます。
主キーの値のみで、列の他の値はフォームには渡されません。アイテムの設定に他の列も含めることはできますが、通常はフォームの初期化プロセスにより、データベースに保存されている現行の値に置き換えられるため、効果はありません。
フォームのページでは、レンダリング前にタイプがフォーム - 初期化のプロセスが実行されます。このプロセスでは、識別のフォーム・リージョンに設定されたリージョンのソースよりデータを取り出し、リージョンに含まれているページ・アイテムに値を設定します。
リージョンEMPDEPT_DVのソースの位置はJSONソース、JSONソースはEMPDEPT_DVとなっています。
リージョンに含まれるページ・アイテムには、それぞれリージョンのソースに含まれる列が設定されています。主キーとして扱われるページ・アイテム(今回のソースではC_ID_EMPNO)は、主キーがオンになります。
APEXはフォームを開いた時点の更新可能なページ・アイテムの値から、楽観的ロックに使用するチェックサムを生成します。二面性ビューEMPDEPT_DVでは、主キーP1_C_ID_EMPNO以外の列がチェックサム生成の対象となります。この中に検索するたびに値が変わるC_METADATA_ASOFも含まれています。
フォームのページにコードを追加して、計算されたチェックサムを表示させてみます。
フォームのページに静的コンテンツのリージョンを作成します。ソースのHTMLコードとして以下を記述します。
<div id="showChecksum"></div>
ページ・プロパティのJavaScriptのページ・ロード時に実行に以下を記述します。
const v = document.getElementById("pPageFormRegionChecksums");
document.getElementById("showChecksum").innerText = v.value;
idがpPageFormRegionChecksums、タイプがhiddenのINPUT要素のvalueを、静的コンテンツのリージョンにコピーしています。(APEX 24.2.2での実装です。内部の仕組みなので、APEXのバージョンごとに変わることがあります。)
この値はINPUT要素として設定されているため、ページの送信時にページ・アイテムの値と共に、サーバーに送信されます。
以上の変更を加えてフォームを開きます。
フォームが開いたときに計算されたチェックサムが表示されます。
そのまま変更の適用をクリックします。ページ・アイテムの値は変更していないにも関わらず、同時実行制御に関するエラーが発生します。
- ユーザーが更新処理を開始してから、データベース内の現行バージョンのデータが変更されています。
フォームから送信されたデータは、(デフォルトでは)タイプがフォーム - 行の自動処理(DML)のプロセスで処理されます。
設定の失われた更新の防止がオンのときは、データ・ソースを更新する前に、送信されたデータに含まれる主キーC_ID_EMPNOの値を条件として、リージョンのデータ・ソースよりJOB、MGR、SAL、COMM、DEPT_LOC、DEPT_DNAME、DEPT_DEPTNO、DNAME、DEPTNO、C_METADATA_ASOF、C_METADATA_ETAGを取り出し、チェックサムを再計算します。行のロックがはいのときは、この検索時に、選択した列の排他ロックを取得します。チェックサムの再計算と一致を確認してから実際にデータを更新するまでのわずかな間ですが、その間に他のセッションからデータが更新されないようにしています。
チェックサムが一致していないと、先ほどのエラーが発生します。
チェックサムの再計算時にC_METADATA_ASOFを取得し直しているため、フォームを開いた時のチェックサムと、再計算されたチェックサムは必ず異なります。結果として、APEXによる同時実行制御のエラーが発生します。
以下の2通りの対策が考えられます。
- 失われた更新の防止をオフにして、APEXによる同時実行制御を無効にする。JSON二面性ビューによる同時実行制御は有効です。
- C_METADATA_ASOFをチェックサムの対象から外す。
C_METADATA_ASOFをチェックサムの対象から外す方法はいくつかあります。
単純に、ページ・アイテムP3_C_METADATA_ASOFをコメント・アウトすると、ページ・アイテムとして表示もされなくなり、チェックサムの対象からも外れます。
タイプを表示のみに変更し、設定のページの送信時に送信をオフにしても、チェックサムの対象から外れます。
ソースの問合せのみをオンにしても、チェックサムの対象から外れます。
C_METADATA_ASOFをチェックサムの計算の対象から外して、フォームによる更新を確認します。
従業員SCOTTのフォームを開きます。給与を変更して変更の適用をクリックします。
今度はエラーは発生せず、給与も指定した通りに更新されます。
再度、従業員SCOTTのフォームを開きます。まだ、変更の確定はクリックしません。
別のセッションより、従業員SCOTTの給与を更新します。
update emp set sal = 10000 where ename = 'SCOTT';
SQL> update emp set sal = 10000 where ename = 'SCOTT';
1行更新しました。
コミットが完了しました。
SQL>
開いている従業員SCOTTのフォームの給与を変更し、
変更の適用をクリックします。
データベースに保存されている給与の値はフォームを開いた時点での給与の値と異なるため、再計算されたチェックサムが一致せず、APEXによる同時実行制御のエラーが発生します。
プロセスプロセス・フォームEMPDEPT_DVの設定の失われた更新の防止をオフにして、APEXによる同時実行制御を無効化します。
先ほどと同じ操作を行います。
APEXによる同時実行制御は行われていませんが、送信データにETAGが含まれているため、JSON二面性ビューによる同時実行制御は有効です。
そのため、ORA-42699のエラー(ETAGの不一致)が発生し更新が拒否されます。
APEXアプリケーションよりETAG(ページ・アイテムC_METADATA_ETAGの値)を送信対象から外すと、二面性ビューの更新データにETAGが含まれなくなります。
更新データのETAG(_metadata.etagの値)が含まれていない場合、そのままデータが更新されます。
ページ・アイテムP3_C_METADATA_ETAGをコメント・アウトし、同じ操作を行います。
従業員
SCOTTのフォームを開きます。

別のセッションより、従業員SCOTTの給与を更新します。
update emp set sal = 2000 where ename = 'SCOTT';
SQL> update emp set sal = 2000 where ename = 'SCOTT';
1行更新しました。
コミットが完了しました。
SQL>
開いている従業員SCOTTのフォームの給与を変更し、変更の適用をクリックします。
今度はデータが更新されます。
この状態は、APEXおよび二面性ビューの双方の、楽観的同時実行制御が適用されていない状態です。
基本的にAPEXアプリケーションでは、APEXの同時実行制御が有効であれば十分です。APEXの同時実行制御が適用できない場合に、JSON二面性ビューの同時実行制御の活用を検討することになるでしょう。
JSONコレクション表
JSONコレクション表はETAGは生成しますが、楽観的同時実行制御は実装されていません。_metadataにasofも含まれていないため、APEXの楽観的同時実行制御はそのまま適用できます。
JSONコレクション表での同時実行制御は、生成されたETAGを使って開発者が実装する必要があります。
試しにJSONコレクション表のETAGを活用して、APEXに同時実行制御を組み込んでみます。
フォームの更新を行うプロセスの直前に、ETAGをチェックするプロセスを作成します。ソースのPL/SQLコードとして以下を記述します。送信されたフォームに含まれるETAGと、データベースに保存されているETAGを比較し、一致しない場合に例外を上げています。
サーバー側の条件のタイプにアイテムはNULLではないを選択し、アイテムとして主キーであるP5_C_EMPNOを指定します。
動作を確認します。
JSONコレクション表のレポートを開き、従業員SCOTTのフォームを開きます。
別ウィンドウより、従業員SCOTTの給与を更新します。
update empdept_j d set d.data = json_transform(d.data, set '$.sal' = 10000) where d.data."_id"."empno".number() = 7788;SQL> update empdept_j d set d.data = json_transform(d.data, set '$.sal' = 10000) where d.data."_id"."empno".number() = 7788;
1行更新しました。
コミットが完了しました。
SQL>
フォームの変更の確定をクリックすると、raise_application_errorで定義しているORA-20001: ETAG不一致によるトランザクション失敗が発生します。
JSONコレクション表でも、APEXの同時実行制御が有効であれば十分です。APEXの同時実行制御が適用できない場合に、JSONコレクション表のETAGを使った同時実行制御の実装を検討することになるでしょう。
今回の記事は以上になります。
今回の検証に使用したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/json-etag-occ.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完