2025年2月28日金曜日

APEXの楽観的同時実行制御とJSON二面性ビューおよびJSONコレクション表のETAGの扱いについて

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のみなのでheadingoff、楽観的同時実行制御の確認なのでautocommitonにします。出力に改行が含まれるとうまくコピペができないので、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は生成しますが、同時実行制御の実装は開発者に任されています。


APEXアプリケーションの作成



二面性ビュー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_DVJSONソース・タイプJSON列のある表を選択します。JSON列のある表としてEMPDEPT_DVを選択します。

へ進みます。


JSON列1DATA(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_JJSONソース・タイプ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を選択します。

へ進みます。


主キー列1C_ID_EMPNO (Number)です。

ページの作成を実行します。


同様の手順でJSONコレクション表EMPDEPT_Jをデータ・ソースとしたページを作成します。

ページ定義名前JSONコレクション表フォーム・ページ名EMPDEPT_JJSONソースとして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のアプリケーション作成の参考になれば幸いです。