オラクルAPEXのトランザクション処理（COMMIT、ROLLBACK）




# 初めに
現代のウエブ開発には、無数の言語、ライブラリー、テクノロジがあります。UIデザインやユーザー・エクスペリエンス等、技術以外の知識も把握しなければならなりません。開発者は、エキスパートになるまで数年がかかってしまいます。

Oracle APEXとはロー・コード開発プラットフォームです。APEXで、簡単で、高速にアプリケーションをゼロから作成し、技術の詳細を気にせずビジネス課題に集中することだできます。一方、プラットフォームなので、普段開発者が管理できることはプラットフォームが管理を引き受けますので、行動が自動か、手動に行うことを把握しなければなりません。

データの保全することにトランザクション処理が重要なことなので、APEXのトランザクション処理を洗い出しましょう。

# トランザクションとは？
データベースのトランザクションとは、複数のSQL文を1つの処理としてまとめてデータベースに反映させることです。

例えば、銀行のシステムに、あるアカウントから他のアカウントに振り込む行動を想像してください。一つだけじゃなくて、２つの変更が必要です：

1. アカウントAの残高を減らす
2. アカウントBの残高を増やす

以上の操作の中に、すべての操作を行わなければならなりません。だからSQL文を1つの処理としてまとめることになり、トランザクションと呼ばれます。


# CommitとRollback
APEXのトランザクション処理を理解するには、まず`commit`と`rollback`というSQLコマンドを理解しなければなりません。
このコマンドでトランザクションを**終了**することができます。

`commit`は、データの変更がまとめてデータベースに**格納**されます。
`rollback`は、トランザクション内でのデータの変更がすべて**キャンセル**されます。

# APEXのトランザクション
APEXのページには、「プロセス」と「動的アクション」でPL/SQL文を書くことができます。

「プロセス」と「動的アクション」もPL/SQL文を実行すると、トランザクションが開始されます。もちろん以上の`commit`と`rollback`コマンドで、手動的にトランザクションを終了することができますが、自動的に`commit`と`rollback`が実行されます。こういう場合はデータが案外な状態になってしまう恐れがありますので、注意が必要です。

# APEXは、いつ自動的にコミットする？

APEXのトランザクション処理の情報はドキュメンテーションにはありませんので、調査で以下のタイミングで自動的にコミットすることを確認しました：

1. COMMITを書く時
2. 動的アクションの**アクション**の終了の時
3. **すべてのプロセス**の終了の時（レンダリング前）
4. **すべてのプロセス**の終了の時（ページ送信後）
5. 一つのプロセスが終了し、**セッション・ステートが更新された**時
6. `APEX_MAIL.PUSH_QUEUE`の実行時
    - まだ確認していないですが、[この記事によって](http://www.danielmcghan.us/2012/08/implicit-commits-in-apex.html)コミットしてしまうらしいです。

# APEXは、いつ自動的にロールバックする？
エラーが処理中に発生したら、自動的にロールバックされる。どこまでロールバックするのは、最後のコミットの時まで。まだコミットしなかった場合はトランザクションの前の状態までロールバックします。


# テストケースのセットアップ
自動`commit`と`rollback`を理解するため、テストケースで確認しましょう。

## テストテーブル
テーブル名：「TEST_COMMIT_ROLLBACK」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650026/f8327516-7cb5-9a7c-6c7d-27860f880995.png)


## テストプロシージャ

```sql
create or replace procedure "TCR_SIMPLE_UPDATE"
(in_value IN NUMBER,
in_value2 IN VARCHAR2)
is
begin
    update test_commit_rollback set value = in_value;
    update test_commit_rollback set value2 = in_value2;
end;
```
## テストページ
APEXのページを作成し、以下のようなフォームリージョンを追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650026/8fa6954b-7a0c-d965-13c9-669c65d09363.png)


# プロセス
プロセスは原則、全部のプロセスが終わった時にコミットする。複数のプロセスがあっても、自動コミットは一回だけです。

### 例
- DBの状況：`Value = 1, Value2 = a`
- ページ送信後、プロセスを2つ実行する
    - タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(:P2_VALUE,:P2_VALUE2);
    ```
    - タイプ：PL/SQL実行
    ```
    ROLLBACK;
    ```
- VALUEを2、VALUE2をbに更新する
- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650026/2901e3da-8362-56d0-e96b-b928b9a8ad59.png)


結果：
- VALUE = 1, VALUE2 = a
- 最初までROLLBACKする。プロセス後コミットされないらしい。

プロセスの実行タイミングが違う場合もコミットは一回です。以下の例で、After Submitのタイミングじゃなくて、Processingのタイミングの後、コミットすることになります。

### 例
- DBの状況：`Value = 1, Value2 = a`
- 「After Submit」のタイミングに、プロセスを作成
    ```
    TCR_SIMPLE_UPDATE(2,'b');
    ```
- 「Processing」のタイミングに、プロセスを作成
    ```
    TCR_SIMPLE_UPDATE(3,'c');
    ROLLBACK;
    ```
- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1650026/1f341426-da6b-a17f-ed77-b8043198a5c5.png)

結果：
- VALUE = 1, VALUE2 = a

 ※プロセスの実行順序はAfter Submit > Validation > Processing

## セッション変更時のコミット
 前に、「プロセスは原則、全部のプロセスが終わった時にコミットする」と書きましたが、例外があります。セッション・ステートを変更すると、コミットしてしまう場合があります。

#### セッション・ステート変更する操作
 
 1. `PX_ITEM := ○○` （直接アイテムを兼行する）
 2. `apex_util.set_session_state(PX_ITEM, 値)`
     - ※`apex_util.set_session_state(PX_ITEM, 値, false)`は安全です [（ドキュメンテーション）](https://docs.oracle.com/cd/E86255_01/AEAPI/SET_SESSION_STATE-Procedure.htm)
 3. `SELECT INTO :PX_ITEM`文
 4. `RETURNING INTO :PX_ITEM`文
 5. Out変数をページアイテムに挿入する
 6. APEX Computation（計算）

#### 例：別プロセスでセッション・ステートを変更する
- ページ送信後、プロセスが実行する
    - DBの状況：`Value = 1, Value2 = a`
    - 「コード実行１」タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(2,'b');
    ```
    - 「ステート変更１」タイプ：PL/SQL実行
    ```
    :P2_ITEM := 2;
    ```
    - 「コード実行２」タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(3,'c');
    ```
    - 「ステート変更２」タイプ：PL/SQL実行
    ```
    :P2_ITEM := 3;
    ```
    - 「コード実行３」タイプ：PL/SQL実行
    ```
    ROLLBACK;
    ```

結果：
- VALUE = 3, VALUE2 = c
- 期待は、`VALUE = 1, VALUE2 = a`が、ステート変更後、コミットしてしまうことがあります。

#### 例：別プロセスでセッション・ステートの値を変更しない
- ページ送信後、プロセスが実行する
    - DBの状況：`Value = 1, Value2 = a`
    - 「コード実行１」タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(2,'b');
    ```
    - 「ステート変更１」タイプ：PL/SQL実行
    ```
    :P2_ITEM := 2;
    ```
    - 「コード実行２」タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(3,'c');
    ```
    - 「ステート変更２」タイプ：PL/SQL実行
    ```
    :P2_ITEM := 2; --以上と変更はなし
    ```
    - 「コード実行３」タイプ：PL/SQL実行
    ```
    ROLLBACK;
    ```

結果：
- VALUE = 2, VALUE2 = b
- 期待は、`VALUE = 1, VALUE2 = a`が、「ステート変更１」後、コミットしてしまうことがあります
- 「ステート変更２」は、ステート変更使用としましたが、値は現在の値と同じなので、コミットは実行しません。

#### 例：プロセス内
- ページ送信後、プロセスが実行する
    - DBの状況：`Value = 1, Value2 = a`
    - 「コード実行」タイプ：PL/SQL実行
  
```
        TCR_SIMPLE_UPDATE(2,'b');
        :P2_ITEM := 2;
        TCR_SIMPLE_UPDATE(3,'c');
        :P2_ITEM := 3;
        ROLLBACK;
```

結果：
- VALUE = 1, VALUE2 = a
- コミットはプロセスが終わった時に実行します。コードをまとめて一つのプロセスで実行すると、コミットする機会はなさそうです。

## 動的アクション
動的アクションは、プロセスと違って、各アクションの後コミットされます。
#### 例
- DBの状況：`Value = 1, Value2 = a`
- ボタンを押すと、動的アクションが実行する
    - タイプ：PL/SQL実行
    ```
    TCR_SIMPLE_UPDATE(2,'b');
    ```
    - タイプ：PL/SQL実行
    ```
    ROLLBACK;
    ```

結果：
- VALUE = 2, VALUE2 = b
- 最初のアクションが終了したからCOMMITします。



# 参照（英語版）

1. Ted Struik Blog : Rollback & Commit
    - https://tedstruik-oracle.nl/ords/f?p=25384:1065::::::
    - 更新：2018-05-30
2. How to make a deferred commit Q&A
    - https://community.oracle.com/tech/developers/discussion/4211718/how-to-deferred-commit-in-apex
3. When is commit executed? Q&A
    - https://community.oracle.com/tech/developers/discussion/710781/when-commit-is-executed
    - 更新：2008
4. Jeff Kemp Blog ： Session State May or May Not Commit
    - https://jeffkemponoracle.com/2014/02/apex_util-set_session_state-may-or-may-not-commit/
5. Implicit commits in APEX
    - http://www.danielmcghan.us/2012/08/implicit-commits-in-apex.htm