モデル間の計算(トランザクション)

最終更新日: 2021年12月15日
R8 | R9

在庫管理のイメージ

出庫モデル syukko の登録処理時に、同時に(関連する)在庫モデル zaiko の値を変更する例を説明します。

図1 在庫管理システムのイメージ

「商品」と「商品在庫」は 1:1 の関係があります。このため、「商品在庫」の主キーは、「商品」を参照するようにしています。

ここではトランザクション系である「出庫伝票」に注目します。このモデルの新規登録処理で、商品在庫を同時に変動させる必要があります。また、商品在庫を超える出庫要求については、エラーを返すようにします。

商品在庫マスタモデルに、図2に示す初期データを設定しました。それぞれ在庫数を100としています。

図2 商品在庫マスタモデルの初期値

出庫伝票を作成します。指定商品を一つ、出庫します。(図3)

図3 出庫伝票の新規作成

出庫伝票が作成されました。このタイミングでトランザクション処理が正常に終了しています。

図4 出庫伝票作成時にトランザクション処理も動作している

商品在庫マスタを確認します。在庫が減じられていることがわかります。

図5 在庫が減じられている

エラー時の扱い

在庫数を超える数を出庫しようとするとエラーメッセージを返すようにしました。(図6)
この場合はデータベースの値は書き変わりません。

図6 トランザクション失敗

定義方法

図1に示した4つのモデルを定義します。

図7 定義した4つのモデル

倉庫マスタ

倉庫IDと倉庫名、倉庫の管理者を定義しました。

図8 倉庫マスタ

商品マスタ

商品ID、商品名、単価を保持します。また、一つの倉庫に紐付けられます。

図9 商品マスタ

商品在庫マスタ

主キーである「商品」は、商品マスタと 1:1 の関連を持ちます。

主キー「商品」項目は、順序を使わない設定としてください。

図10 商品在庫マスタ

出庫伝票

どの商品をいくつ出庫したかを記録します。

図11 出庫伝票

ここで商品の出庫数に合わせて、商品在庫マスタの在庫数量を減らすために、モデル参照項目「商品」にトランザクションの設定を行います。(図12)
ここでは、新規登録時に「在庫」モデルと連動する指定を行います。

図12 トランザクション設定

ワンポイント

モデル名を明記しているのは、今回のケースにおいては(参照先のモデルが)「商品」ではなく「商品在庫」となっているためです。「商品」と「商品在庫」は 1:1 の関係にあり、「商品在庫」の主キーは、「商品」の主キーと同じという関係としています。
参照先モデルがそのままトランザクション処理の対象モデルに等しい場合は、モデル名を空白とすることもできます。本欄が空白の場合は、参照モデル名が利用されます。

新しくトランザクションを設定したとき(新規登録・更新・削除・コピー登録のチェックを行ったとき)は、ビルド処理を行ってください。
一度設定したあとは、ビルド処理は不要です。トランザクションスクリプトの変更は即座に反映されます。

図13 ビルド処理

文法

実際のトランザクション処理コード(トランザクションスクリプト)は、図12における「トランザクション制御時のスクリプト」欄に記述します。このコードはトランザクション境界の中で実行されます。[トランザクション境界について...]

var suryou = zaiko.suryou;
var syukko_num = syukko.syukkoNum;
if (suryou - syukko_num < 0) {
  return "在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
zaiko.suryou = suryou - syukko_num;
return null;
  • スクリプトでは、return 命令で一つの戻り値を返すようにします。戻り値が null の場合は正常と扱われます。エラーの場合はエラーメッセージ(文字列)を返すようにします。
  • 「新規登録」「更新」「削除」のいずれかを指定します。それぞれのタイミングでトランザクションスクリプトを記述できます。

新規登録とコピー登録

新規登録とコピー登録の区別はありません。SCREENTYPE 定数を用いて判別することができます。これは画面種別を返すSCREENTYPE関数の値を保持しています。

新規登録時とコピー登録時は同じスクリプトファイルが呼ばれますが、コピー登録時は処理しないような制御を行う例を示します。

if (SCREENTYPE == "copy") {
    /* コピー登録時は処理を行わない */
    return null;
}
/* 登録時にのみ実行される */
...
}
※ トランザクション制御設定欄の「コピー登録」チェックボックスは、設定しても無視されます。この設定欄は過去の互換性のために残っていましたが、将来の Wagby で削除されます。

繰り返しコンテナとの連携

繰り返しコンテナ内の項目にトランザクションを指定することができます。
図14は、出庫伝票の明細で、複数の商品を同時に指定する例です。

図16 出庫伝票に明細を(繰り返しコンテナとして)用意する

同時に複数のデータが書き換えられていることがわかります。(図15)

図17 トランザクションが成功している

定義方法

図11の出庫伝票の定義を変更し、明細を用意しました。(図16)

図18 明細を用意した出庫伝票の定義

トランザクションスクリプトの変更

ファイル名やファイルの保存位置は変わりません。
スクリプト内では、繰り返しコンテナ "details" を利用できます。このとき、複数の繰り返しコンテナのデータ毎に、このスクリプトが実行されます。(図16では3つの明細データがあったため、各データ単位でこのスクリプトが合計3回、実行されます。)

/* トランザクションで使うオブジェクト sykko, zaiko は利用可能になっている。
   これは定義ファイル「トランザクション指定」に含めたモデルである。
   繰り返しコンテナも利用できる。details という名前のオブジェクトが用意されている。*/
var suryou = zaiko.suryou;
/* detailsは明細データ毎に呼び出された値がセットされる */
var syukko_num = details.syukkoNum;
if (suryou - syukko_num < 0) {
    return "在庫 "+suryou+" に対して "+syukko_num+" を出庫しようとしました。";
}
zaiko.suryou = suryou - syukko_num;
return null;

外部キー子モデルとの連携

Wagbyの外部キー関係では、一つの親データが複数の子データを管理できます。 次の例では親モデル名 parent と、子モデル名 child を用いて説明します。

子モデル child は、親モデルを指し示す外部キー項目 pkey を持つとします。このとき、この pkey 項目に対してトランザクションスクリプトを記載できます。

図19 親子モデル関係でのトランザクションスクリプトサンプル

上図の例は、子モデル新規登録のタイミングで、子モデルの値を親モデルの項目へ転記するものです。

parent.pname = child.cname;

対象データがロックされている場合の動作

Wagby の標準では、モデルの更新制御は悲観ロックを用います。これを楽観ロック方式に切り替えることもできます。トランザクション実行時のロックは、指定した方式に準拠して制御されます。

悲観ロックでは、対象となるデータが更新中(更新画面が開いた状態)の場合、登録・更新処理を行おうとすると画面には "失敗した" というエラーメッセージが表示されます。

楽観ロックでは、対象となるデータが別に更新されていた場合、同様にエラーとなります。

いずれの場合も、そのトランザクションは失敗します。これは正しい動作です。

Service/DaoクラスやSQLを利用する

ここまでの説明は Wagby が標準で提供するモデル参照という仕組みを用いて自身に紐づくモデルが定まっている場合に適用できるものです。そのためトランザクション処理を記述する箇所は限定されています。

一方で、トランザクションスクリプトで(自身に紐づくモデル以外の)任意のモデルを操作する場合は Dao クラスを使うことができます。さらに(トランザクションスクリプトではない)他のスクリプトでも Service クラスを使ってトランザクション処理を記述することもできます。これらの Dao や Service を使うと SQL を利用することもできます。

トランザクション境界

Service/Daoクラス、ヘルパ、SQLを利用する > トランザクション境界 をお読みください。

仕様・制約

生成されるスクリプトファイル

トランザクションを設定した場合、生成されるファイル名は次の通りです。

モデルIDEntityService_doTransactionBy${トランザクションを設定した項目ID}InsertTransaction.js
モデルIDEntityService_doTransactionBy${トランザクションを設定した項目ID}UpdateTransaction.js
モデルIDEntityService_doTransactionBy${トランザクションを設定した項目ID}DeleteTransaction.js