2023年2月21日火曜日

APEXのプロセスで発生する暗黙コミットの注意点

追記: APEX 23.1よりアプリケーション定義のプロパティにセッション・ステート・コミットが追加されました。以下はセッション・ステート・コミット即時の動作になります。セッション・ステート・コミットリクエストの終わりの場合は、暗黙コミットは発生しません。

APEX 23.1で追加されたセッション・ステート・コミットの注意点

-----

 Twitterを見ていたら、ページを送信したときにエラーが発生したけどトランザクションがロールバックしない、というツィートを見かけました。

このような事象は実際に発生します。プロセス中でページ・アイテムを操作すると、暗黙でトランザクションがコミットされることがあるためです。

テストのためのアプリケーションを作成しました。

今回はアプリケーションを作成することが目的ではないため、作り方については省略します。

以下のDDLで表TEST_MESSAGESを作成し、ページ・アイテムに記述した文字列を列MESSAGEに書き込んでいます。

表TEST_MESSAGESの内容は、クラシック・レポートで表示しています。

create table test_messages (
    id                             number generated by default on null as identity 
                                   constraint test_messages_id_pk primary key,
    message                        varchar2(4000 char)
)
;

ボタンSubmitをクリックすると、4つのプロセスを実行します。


それぞれのプロセスが実行するのは、1行のコマンドです。上から順にコマンドを記載します。

プロセスUpdate Page Itemにて、ページ・アイテムP1_TEXTに値として処理完了を設定します。

delete from test_messages;
insert into test_messages(message) values(:P1_TEXT);
:P1_TEXT := '処理完了';
rollback;

上記の記述方法でページ・アイテムに値を設定すると、この時点でトランザクションがコミットされます。より正確には、すでにP1_TEXTに保持されている値と異なる値が設定されたときに、トランザクションがコミットされます

例えばページ・アイテムP1_TEXTこのメッセージを記録する。と書いてボタンSubmitをクリックします。


rollbackは実行されますが、ページ・アイテムP1_TEXT処理完了と書き込まれている時点でトランザクションはコミットされているため、レポートにこのメッセージを記録する。と表示されます。


しかし、ページ・アイテムP1_TEXT処理完了と書いてSubmitをクリックしても、レポートには何も表示されません。


TEST_MESSAGESには処理完了と書き込まれてはいますが、P1_TEXTの値に変更がないためトランザクションはコミットされません。そのため後続のrollbackで、表への書き込みもロールバックされます。

apex_util.set_session_stateの第3引数(p_commit)にtrueを指定した場合も、同様の動作になります。

delete from test_messages;
insert into test_messages(message) values(:P1_TEXT);
apex_util.set_session_state('P1_TEXT','処理完了',true);
rollback;

バインド変数に値を指定している場合は、同じプロセス内でrollbackを実行するとトランザクションのコミットはされません。apex_util.set_session_stateの第3引数がtrueの場合は、同じプロセス内であってもファンクションが呼び出された時点でトランザクションがコミットされるため、その後にrollbackを実行してもapex_util.set_session_state以前の処理はロールバックされません。

apex_util.set_session_stateの第3引数がfalseの場合は、トランザクションはコミットされません。そのため、delete、insertの処理も含めてトランザクションがロールバックされます。

delete from test_messages;
insert into test_messages(message) values(:P1_TEXT);
apex_util.set_session_state('P1_TEXT','処理完了',false);
rollback;

OracleのDan McGhanさんによるブログ記事に、より詳しい解説があります。

Implicit Commits in APEX

APEXの暗黙でコミットを実行するポイントを7つ挙げています。
  1. ページのロード時、レンダリングが完了後
  2. ページの送信時、ブランチが呼び出されて別ページに移る前
  3. ページの送信時、検証(Validation)が失敗してページの再描画が行われる前
  4. バインド変数の操作を含むプロセス(Process)の終了時
  5. 計算(Computation)の終了時
  6. APEX_UTIL.SET_SESSION_STATEが呼ばれたとき(この記事が書かれたときは引数p_commitはなく、常にコミット)
  7. APEX_MAIL.PUSH_QUEUEが呼ばれた時
今回動作確認のために使用したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/test-implicit-commit.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。