2021年2月10日水曜日

制約の違反に対応したエラー・メッセージを定義する

オランダ在住のRoel Hartmanさん(ODTUGのボード・メンバーです)が彼のブログに以下の記事を書いていました。

Stop using validations for checking constraints!

記事の内容は、データベースの表に設定している制約に違反したときに発生するエラー・メッセージを画面に表示する際には、Oracle APEXが提供している(アプリケーション定義に含まれる)エラー処理ファンクションを活用しましょう、というものです。関連するドキュメントの記載としては、パッケージAPEX_ERRORの説明になります。

彼の記事の中で面白いなと思ったのは、出力するメッセージの取得にAPEX_LANG.MESSAGEを使用しているところです。このファンクションは元々はアプリケーションの翻訳に使用するものです。翻訳で使用する方法については、以前にこのような記事を書きました。もちろん、アプリケーションの翻訳をしなくても、キーとなる値を指定して、メッセージとする文字列を取得することができます。

実際に実装して効果を確認してみましょう。


確認用アプリケーションの作成


Oracle APEXのワークスペースにサインインし、SQLワークショップからユーティリティクイックSQLを開きます。

以下のクイックSQLのモデルを使って、表AET_EMPとAET_EMP_ATTRIBUTESを作成します。表AET_EMP_ATTRIBUTESには、各種の制約を付加しておきます。これらの制約に違反したときに表示されるエラー・メッセージを、Oracle Databaseが返すメッセージから、利用者向けに分かりやすいメッセージに変更することが本記事のゴールです。

# prefix: aet
# prefixPKwithTname: true
# semantics: default
# language: ja
emp /insert 5
  emp_id /pk /values 1,2,3,4,5
  emp_name

emp_attributes
  emp_attr_id /pk 
  emp_id /fk emp
  national_id num /unique
  attribute_name vc20 /nn
  attribute_value vc80
  gender vc1 /check M,F 
  age num /between 0 and 120

制約としては、主キー、外部キー、一意、NULL不可、チェック、範囲指定を設定しています。必要なディレクティブはモデルに含んでいるので、クイックSQLでSQLの生成SQLスクリプトの保存レビューおよび実行即時実行とボタンをクリックしていけば、今回使用する表が作成されます。

続いてアプリケーション・ビルダーより、空のアプリケーションを作成します。アプリケーションの作成より新規アプリケーションを選び、アプリケーションの名前だけを指定して、アプリケーションの作成を実行します。ここではアプリケーションの名前を、エラー処理の確認としています。

空のアプリケーションが作成されたら、確認に使用するフォームと対話グリッドのページを作成します。最初にフォームのページを作成します。アプリケーション・ビルダーの画面より、ページの作成を開始します。

コンポーネントフォームを選択します。

フォーム付きレポートを選択します。

レポート・タイプ対話モード・レポートフォーム・ページ・モードモーダル・ダイアログとします。それ以外は任意の値を設定できます。に進みます。

ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択します。に進みます。


データ・ソースローカル・データベースソース・タイプとし、表/ビューの名前としてAET_EMP_ATTRIBUTES(表)を選択します。に進みます。


主キー制約でのエラーも発生させるため、主キー型としてデータベースで管理(ROWID)を選択します。作成を実行します。


以上でレポートとフォームの2つのページが作成されます。続いて、対話グリッドのページを作成します。フォームの作成と同様に、ページの作成を開始し、フォームの選択に続いて対話グリッドを選びます。


ページ名は任意ですが、ここではグリッド編集とします。に進みます。


ナビゲーションのプリファレンスとして新規ナビゲーション・メニュー・エントリの作成を選択します。に進みます。


データ・ソースとしてローカル・データベース編集が有効ONソース・タイプとし、表/ビューの名前としてAET_EMP_ATTRIBUTES(表)を選択します。こちらも主キー制約のエラーを発生させるため、あえて主キー列ROWIDを選択します。作成を実行します。


対話グリッドが作成されます。作成されたアプリケーションを実行し、表の制約に違反したデータを投入し、エラーの表示を確認してみましょう。

すでに作成済みの行を重複させて保存を実行すると、ORA-00001: 一意制約(DBCLOUD_TEST.AET_EMP_ATTRIBU_EMP_ATTR_ID_PK)に反しています、が発生しました。


Ageに400を指定して作成を実行すると、ORA-02290: チェック制約(DBCLOUD_TEST.AET_EMP_ATTRIBUTES_AGE_BET)に違反しました、が発生しました。



以上で確認用アプリケーションは完成です。

エラー処理ファンクションの設定


マニュアルに記載されている例を元に、エラー処理ファンクションを定義します。コードに含まれているコメントが日本語マニュアルでも翻訳されていないので、処理内容がわかるように日本語に翻訳しました。




元の記事ではapex_lang.create_messageの引数であるp_languageにapex_util.get_preference('FSP_LANGUAGE_PREFERENCE')を与えていますが、これはアプリケーション言語の導出元アプリケーション・プリファレンス(FSP_LANGUAGE_PREFERENCEを使用)になっていて初めて有効なので、アプリケーションのプライマリ言語になっているときはNULLを返します。日本では大抵、プライマリ言語として日本語(ja)を設定しているため、単にjaを指定する様に変更しました。


定義したファンクションを、アプリケーション定義のエラー処理ファンクションとして設定します。


アプリケーションを実行して、再度、制約に関するエラーを発生させます。最初は制約の名前が表示されます。


再度、エラーを発生させると、次はTODOと表示されます。


テキスト・メッセージの登録


アプリケーション・ビルダーを開いて、共有コンポーネントグローバリゼーションに含まれるテキスト・メッセージを開きます。

エラーの原因となった制約名にTODOというテキストが登録されていることが確認できます。

このTODOを適切なメッセージに置き換えます。今回は制約AET_EMP_ATTRIBU_EMP_ATTR_ID_PKにたいして、従業員属性のIDとなる値は一意である必要があります。とテキストを設定します。変更の適用を行います。

再度、同じ操作を行うと、エラー・メッセージが変更されていることが確認できます。

フォームを使って同じデータを作成しようとしても、表示されるエラー・メッセージは同じです。


以上でエラー処理ファンクションを使った、エラー・メッセージの変更方法の紹介は完了です。

他にも設定した制約があります。演習を行うつもりで、メッセージを設定してみてください。


発生したエラーの確認方法


ひとつ追加で説明すると、Oracle APEXのアプリケーションで発生したエラーはすべて記録されます。

右上の管理者アイコンよりメニューを開き、アクティビティのモニターを呼び出します。


左下にあるアプリケーション・エラーを開きます。


いつ、誰が、どのアプリケーションの、どのページで、どのようなエラーが発生したのか、などの情報が記録されているので、利用者からの報告が無くても、作成した機能の不具合の有無を確認することが可能です。


データベースのスキーマに定義した制約に対応したエラー・メッセージが定義済みであれば、アプリケーションを開発する人にとっては、相当な負担の軽減になるかと思います。是非、活用の検討をしてみてください。

確認のために作成したアプリケーションのエクスポートをこちらに置いておきます。
https://github.com/ujnak/apexapps/blob/master/exports/apexerrormessage.sql

テキスト・メッセージはエクスポートに含まれていません。これは仕様です。