1つのフォームで同時に2つの表を更新する際に、失われた更新の防止(ロストアップデート防止)がどのように働くか、確認してみました。Oracle APEXが標準で提供しているロストアップデートの防止については、以前に記事を書いています。
スキーマ定義
確認のために2つ簡単な表を作成しました。クイックSQLでの定義は以下です。行バージョン番号も生成しています。
# prefix: tst
# rowVersion: true
# semantics: default
concjoin1
value1 vc80
value11 vc80
concjoin2
value2 vc80
value22 vc80
生成されたDDLは以下です。行バージョン番号を付加するトリガーも生成されています。
create table tst_concjoin1 ( id number generated by default on null as identity constraint tst_concjoin1_id_pk primary key, row_version integer not null, value1 varchar2(80), value11 varchar2(80) ) ; create table tst_concjoin2 ( id number generated by default on null as identity constraint tst_concjoin2_id_pk primary key, row_version integer not null, value2 varchar2(80), value22 varchar2(80) ) ; -- triggers create or replace trigger tst_concjoin1_biu before insert or update on tst_concjoin1 for each row begin if inserting then :new.row_version := 1; elsif updating then :new.row_version := nvl(:old.row_version,0) + 1; end if; end tst_concjoin1_biu; / create or replace trigger tst_concjoin2_biu before insert or update on tst_concjoin2 for each row begin if inserting then :new.row_version := 1; elsif updating then :new.row_version := nvl(:old.row_version,0) + 1; end if; end tst_concjoin2_biu; /
対話グリッドのページ作成
以下のSQLをソースとする対話グリッドのページを作成します。
select t1.id, t1.value1, t2.value2 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id
最初に空のアプリケーションを作成します。アプリケーション・ビルダーより、作成、新規アプリケーションの作成を実行し、アプリケーションの設定画面で名前だけ設定し、アプリケーションの作成をクリックします。
作成したアプリケーションより、ページ作成ウィザードを実行します。ページの作成をクリックします。
フォームを選択します。
編集可能対話グリッドを選択します。
ページ名を行の値 - VALUE1 & 2と設定し、次へ進みます。このページに作成する対話グリッドのプロパティ、失われた更新のタイプを行の値(これがデフォルトの設定です)として確認を行うため、この名前にします。
ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択し、次へ進みます。新規ナビゲーション・メニュー・エントリはデフォルトでページ名が設定されます。
データ・ソースとしてローカル・データベースを選択します。編集が有効をONにし、ソース・タイプはSQL問合せとし、SQL SELECT文を入力のところに、SQLを入力します。検証をクリックしてSQLを確認し、作成を実行します。
挿入は必ずエラーになります。挿入の対象が表TST_CONCJOIN1(IDとVALUE1)とTST_CONCJOIN2(VALUE2)の両方になるためです。
insert into ( select t1.id, t1.value1, t2.value2 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id ) t values (1,100,200);
update ( select t1.id, t1.value1, t2.value2 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id ) t set t.value1 = 100, t.value2 = 200 where t.id = 1;
削除の場合は、表TST_CONCJOIN1のIDのみを対象とすることになるため、そちらの表の行のみ削除されます。
delete from ( select t1.id, t1.value1, t2.value2 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id ) t where t.id = 1;
begin case :APEX$ROW_STATUS when 'C' then insert into tst_concjoin1(value1) values(:VALUE1) returning id into :ID; insert into tst_concjoin2(id, value2) values(:ID, :VALUE2); when 'U' then update tst_concjoin1 set value1 = :VALUE1 where id = :ID; update tst_concjoin2 set value2 = :VALUE2 where id = :ID; when 'D' then delete from tst_concjoin1 where id = :ID; delete from tst_concjoin2 where id = :ID; end case; end;
ロストアップデートの防止の動作確認
ユーザーが更新処理を開始してから、データベース内の現行バージョンのデータが変更されています。というエラーが発生します。
select t1.id id, t1.value1 value1, t2.value2 value2, apex_item.md5_checksum(t1.id, t1.value1, t2.value2) cs from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id
select t1.id id, t1.value1 value1, t2.value2 value2, apex_item.md5_checksum(t1.id, t1.value1, t2.value2) cs from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id where t1.id = 1 for update nowait
行の値が有効でないケースの確認
select t1.id, t1.value11, t2.value22 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id
begin case :APEX$ROW_STATUS when 'C' then insert into tst_concjoin1(value11) values(:VALUE11) returning id into :ID; insert into tst_concjoin2(id, value22) values(:ID, :VALUE22); when 'U' then update tst_concjoin1 set value11 = :VALUE11 where id = :ID; update tst_concjoin2 set value22 = :VALUE22 where id = :ID; when 'D' then delete from tst_concjoin1 where id = :ID; delete from tst_concjoin2 where id = :ID; end case; end;
行バージョン番号による防止
select t1.id, t1.row_version + t2.row_version row_version, t1.value1, t2.value2 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id
select t1.id, t1.row_version + t2.row_version row_version, t1.value11, t2.value22 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id
フォーム・リージョンの場合
begin case :APEX$ROW_STATUS when 'C' then insert into tst_concjoin1(value11) values(:P9_VALUE11) returning id into :P9_ID; insert into tst_concjoin2(id, value22) values(:ID, :P9_VALUE22); when 'U' then update tst_concjoin1 set value11 = :P9_VALUE11 where id = :P9_ID; update tst_concjoin2 set value22 = :P9_VALUE22 where id = :P9_ID; when 'D' then delete from tst_concjoin1 where id = :P9_ID; delete from tst_concjoin2 where id = :P9_ID; end case; end;
動的アクションによる更新
begin select t1.value1, t1.value11, t2.value2, t2.value22 into :P14_VALUE1, :P14_VALUE11, :P14_VALUE2, :P14_VALUE22 from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id where t1.id = :P14_ID; end;
begin update tst_concjoin1 set value1 = :P14_VALUE1, value11 = :P14_VALUE11 where id = :P14_ID; update tst_concjoin2 set value2 = :P14_VALUE2, value22 = :P14_VALUE22 where id = :P14_ID; commit; end;
select id d, id r from tst_concjoin1
begin select t1.value1, t1.value11, t2.value2, t2.value22, t1.row_version + t2.row_version into :P15_VALUE1, :P15_VALUE11, :P15_VALUE2, :P15_VALUE22, :P15_ROW_VERSION from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id where t1.id = :P15_ID; end;
更新の際には、ROW_VERSIONの比較を行います。一致しないときはエラーにしています。
declare l_row_version number; expLostUpdate exception; begin select t1.row_version + t2.row_version into l_row_version from tst_concjoin1 t1 join tst_concjoin2 t2 on t1.id = t2.id where t1.id = :P15_ID for update nowait; if l_row_version = :P15_ROW_VERSION then update tst_concjoin1 set value1 = :P15_VALUE1, value11 = :P15_VALUE11 where id = :P15_ID; update tst_concjoin2 set value2 = :P15_VALUE2, value22 = :P15_VALUE22 where id = :P15_ID; else raise expLostUpdate; end if; commit; end;