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を元にしています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace json duality view empdept_dv as
select json{
'_id': json{ 'empno': e.empno },
'ename': e.ename,
'job': e.job,
'sal': e.sal,
'mgr': e.mgr,
'comm': e.comm,
'deptno': e.deptno,
'dept':
(
select json{
'deptno': d.deptno,
'dname': d.dname,
'loc': d.loc
}
from dept d
where d.deptno = e.deptno
)
}
from emp e
with insert update delete;
作成された二面性ビュー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を作成してみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace procedure update_jct_with_etag(
p_json in clob
)
as
-- 必ずしも自律トランザクションにする必要はない。
pragma autonomous_transaction;
e_transaction_failed exception;
l_empno number;
l_etag varchar2(200);
begin
-- 更新するJSONよりidとetagを取り出す。
select empno, etag into l_empno, l_etag from json_table(p_json,'$'
columns (
empno number path '$._id.empno',
etag varchar2(200) path '$._metadata.etag'
)
);
-- etagが一致するときだけ、アップデートを実行する。
update empdept_j d set data = p_json
where d.data."_id"."empno" = l_empno and d.data."_metadata"."etag".string() = l_etag;
if SQL%ROWCOUNT = 0 then
/*
* etagが一致しないときに例外を上げる。
* 主キーが一致しない(行が削除されている)ときもこの例外が上がるので、
* 条件はもっと精査が必要。
*/
raise e_transaction_failed;
end if;
commit;
end 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を比較し、一致しない場合に例外を上げています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare
l_etag varchar2(200);
e_transaction_failed exception;
l_data json;
begin
select data into l_data from empdept_j d
where d.data."_id"."empno".number() = :P5_C_ID_EMPNO for update nowait;
select j.etag into l_etag
from json_table(l_data, '$'
columns (
etag varchar2(200) path '$._metadata.etag'
)
) j;
apex_debug.info('%s = %s', l_etag, :P5_C_METADATA_ETAG);
if l_etag <> :P5_C_METADATA_ETAG then
raise_application_error(-20001, 'ETAG不一致によるトランザクション失敗');
end if;
end;
サーバー側の条件 のタイプ にアイテムは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のアプリケーション作成の参考になれば幸いです。
完