サポート > リポジトリ > モデル > ロック方式

Wagbyでは楽観ロック方式と悲観ロックを方式を選択することができます。

Wagbyではモデル単位でロック方式を指定することができます。標準は悲観ロックです。 両者の違いは次のとおりです。

楽観ロック 悲観ロック(標準)
特徴 同じデータの更新画面を、同時に複数の利用者が開くことができます。最初の更新者のみが書き込みに成功します。それ以外の更新はすべて失敗します。 同じデータの更新画面を開くことができる利用者はただ一人に限定されます。他の利用者は、そのデータを閲覧することはできますが、更新画面を開くことはできません。
書き込みの競合 いったん更新画面をキャンセルし、再び更新画面に入り直す必要があります。修正途中のデータは無効になります。 動作の仕組み上、書き込みの競合は発生しません。最初の利用者が更新を終えるまで、他の利用者は更新処理を待つことになります。
競合時の対応 競合が発生して更新が失敗した場合でも、画面上には入力データが残っているため、利用者の方で復旧処理を工夫することはできます。例えば、入力済みの値をテキストエディタなどに一時コピーし、再び更新処理を行うといったことが考えられます。 更新画面を開いた利用者が、長時間、画面を更新状態にしておくことに一定の時間制限を用意するようにします。これをセッションタイムアウトといいます。Wagbyの標準は20分です。"環境 > アプリケーション > 初期パラメータ"で変更することができます。
ロックの実現方法 モデル内に「バージョン管理用カラム」という項目を用意します。型は8バイト整数です。更新画面を開いたとき、同項目の値を読み込みます。更新処理時に再び値を読み込み、最初に読み込んだ値と変わっていなければ更新を成功させます。 Wagby内部の「ロックマネージャ」を利用します。更新画面を開いた際に、ロックを取得します。更新終了時にロックを解放します。
ロック解放のタイミング -
  • データを更新する(トランザクション終了時にロック解放)
  • メニュー画面を表示する
  • ログオフする(セッションタイムアウトも含む)

更新画面での悲観ロックの挙動を"画面機能 > 更新"で説明しています。

「画面>その他>データベースの詳細」から、ロック方式を選択することができます。

図1 ロック方式の選択

楽観ロックと悲観ロックは、いずれか一方を選択する必要があります。標準では悲観ロックです。(悲観ロックにチェックが付いた状態で、かつ入力不可となっています。)

楽観ロックのチェックボックスを有効にすると、悲観ロックのチェックボックスとバージョン管理用カラム名が入力可となります。ここで入力した項目名で、型が「8バイト整数」の項目が自動的に追加されます。

ここで入力した項目名は「モデル項目」欄には表示されませんが、内部で管理されます。そのため「モデル項目」の欄に、この項目を定義する必要はありません。

バージョン管理用カラム名は必須チェックを行ないます。未入力状態でフォーカスがセットされると「この値は必須です」というエラーメッセージが表示されます。

「悲観ロック」と「楽観ロック」は両方、有効にすることができます。片方のみを使う場合、もう片方のチェックを解除するようにしてください。

サブモデル利用時の注意点

ロック方式はメインモデルとサブモデルで一貫性を保つようにしてください。

例えばサブモデルのみ楽観ロックを有効にした場合、メインモデルに存在しないバージョン管理カラムを利用しようとしてエラーとなります。

通常は悲観ロックとする

Wagbyの標準は「悲観ロック」です。本設定を意識しなくとも、悲観ロックとして運用されます。競合が発生しないため、もっとも問題の少ない方法です。

楽観ロックを選択した方がよい場合

外部システムとの連携を考慮する場合、楽観ロックを用いた方がよい場合があります。

悲観ロックは、Wagbyが内部で用意したロックマネージャを使います。そのため、今どのデータがロックされているかを外部システムが知るためには特別な工夫が必要です。

楽観ロックは、Wagbyと外部のシステムが、データベースに用意されたバージョン管理用カラムを共用できます。いずれのシステムから更新された場合でも、確実に競合を検出することができます。

楽観ロックの実装

Wagbyが利用する Hibernate というミドルウェアが提供する機能を用いています。ただしタイムスタンプ方式ではなく、バージョン方式のみを採用しています。(タイムスタンプ方式は厳密性に欠けると判断しました。)

悲観ロックの実装

Wagby独自のロックマネージャを用います。ロック取得時のキーはメモリに保持されています。特別な内部テーブル jfclockobject でロックキーを管理させることもできます。この場合、外部システムから「どのレコードがロックされているか」を知ることができます。[詳細は次節で説明します。]

データベースの SELECT FOR UPDATE 構文について

いくつかのデータベースには "SELECT ... FOR UPDATE" 構文が提供されています。しかし Wagby のロックは、この機能を使っていません。その理由を説明します。

"SELECT ... FOR UPDATE" はトランザクション内でのみ有効なロックです。 しかし本節で説明しているロック処理は、ユーザの操作を伴うもので、次のような動作が求められるものです。

  1. 更新画面を開く。(ここでデータを SELECT する。)
  2. ユーザがデータを編集する。
  3. 保存ボタン押下でデータを更新する。(データを UPDATE する。)

ここで 1. と 3. ではトランザクションが異なります。このように複数のトランザクションをまたがるような処理は一般に「ロングトランザクション」として扱います。

データベースのトランザクションとは異なるものです。

ロングトランザクションでは、"SELECT ... FOR UPDATE" を使うことができません。 この構文は画面処理ではなく、ある完結した業務処理ロジックの中で使うことを想定したものです。

「保存ボタンを押下した直後に "SELECT ... FOR UPDATE" を実行して...」という考え方ではロストアップデートを防ぐことができず、厳密な悲観ロックとはいえません。

Wagbyではこのようなロングトランザクションでも一貫性を保つため、楽観ロックならびに(独自のロックマネージャを使った)悲観ロックの両方に対応しています。

悲観ロックマネージャの改訂 7.12

R7.12 より、悲観ロック利用時にメモリ内でロックオブジェクトを管理するクラス LockManagerImpl の改訂版 (v2) を用意しています。大量のロック処理が発生したときの実行時パフォーマンスを向上させたものです。R7.12 より標準でこの v2 が用いられるようになります。[旧方式に戻す...]

Wagbyの「悲観ロック」は、内部でロックマネージャをもっています。このロックマネージャはメモリ内にロックを保持します。この情報は外部から知ることはできません。

このロック情報を(リレーショナルデータベースの)テーブルに書き込むオプションを用意しています。これによって、外部プログラムから「どのデータがロックされているか」を知ることができるため、ロックされているデータは更新しないといった制御を行うことができます。

さらに、外部プログラム側からロックをかけることで、Wagby側からも更新ができないように制御することもできます。

定義方法

「環境 > カスタマイズ > 詳細 > ロック情報をデータベースのテーブルに格納する」を有効にします。

図2 ロック情報をデータベースのテーブルに格納する

ロック情報を検索する

システム管理者でログオンし、管理処理タブにある「ロック情報検索」画面を開くと、図3のように現在、ロックされているデータが表示されます。

図3 ロック情報を検索する

jfclockobjectテーブル

図2の設定を有効にすると、利用するリレーショナルデータベース内に jfclockobject テーブルが用意されます。

項目名 主キー 説明
modelname ロック対象のモデル名(英語)
pkey ロック対象データの主キーの値。複合キーの場合は"$SEP$"を区切り文字として値を連結する。
lockForAll 数値型。"1" が個別データのロック。"2" がモデル全体のロックを意味する。
userid ロックを取得した(juserアカウントの)userid値。
username ロックを取得した(juserアカウントの)username値。
sessionid ロックを取得したユーザのセッションID値。

あるデータの更新画面を開くタイミングで、jfclockobject テーブルを検索し、該当データに関するロックが存在するかどうかを確認します。すでにロックが存在していた場合、更新画面を開くことができません。存在しなかった場合、同テーブルにロック情報を書き込みます。更新処理が終了したタイミングで、ロック情報を削除します。

外部からロックを取得する

外部プログラムから jfclockobject テーブルにロック情報(レコード)を追加することで、Wagbyの画面から編集できないようにすることができます。

事前に同テーブルにロック対象データがないか確認してからレコードを追加するようにしてください。

jfclockobject テーブルに用意されたすべての列を埋める必要があります。いずれか一つでも、未設定 (null) であった場合、ロックは取得されません。

項目 modelname, pkey, lockForAll は対象データを指し示すために必要です。
項目 userid, username, sessionid は「誰がロックを取得したか」という情報を記録するために必要です。

なお項目 sessionid は Web アプリケーションサーバが発行するセッションIDが格納されます。しかし外部プログラムからこの値を設定する場合、セッションID を生成することは困難です。例えば Web アプリケーションサーバに Tomcat を利用した場合、セッションIDは32文字のランダム文字列です。原則として、ユニークな文字列であれば動作します。

外部プログラムから値を設定する場合は、何らかのルールを決めると良いでしょう。例えばこの値を次のようにします。

$HOSTNAME$APPLICATIONNAME$SEQUENCEVALUE

このようなルールに基づいて擬似的なセッションID文字列をセットすることで、ユニーク性を保ちつつ、誰がロックしたかも視認しやすくなります。

Wagby Developer Day 2017