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;







































