設問3 予約管理機能の不具合について(1)~(4)に答えよ。

(3) 図9を完成させるために、【s】~【u】に入れる適切な字句又は数字を答えよ。
a郡 b郡
【s】 Status=? LastUpdate=?
【t】 psUp.setInt(10, revStatus); psUp.setLong(10, lastDateTime);
【u】 1 1

解説

排他制御に関する問題です。

本文抜粋

[予約管理機能における不具合の修正]
 Sグループのリーダとしてソースコードレビューに参加していたT主任は、この不具合を修正するために、executeUpdateメソッドでUPDATE文を実行した時、 更新した行数が戻り値になることに着目 した。そして、図5の 25行目のパラメタ付きSQLのWHERE句を修正 した上で、図5の60行目のexecuteUpdateメソッドの実行時に 異常の発生を検知する仕組み を提案した。この提案に基づき修正されたプログラムは図9のとおりである。

図9 修正されたサーブレット"WakuClick"

1~23:(省略)[図5の1~23と同じ]
24:        psSel = conn.prepareStatement("SELECT * FROM rsvList WHERE rsvDate=? AND rsvTime=? AND Biyoshi=?");
25:        psUp = conn.prepareStatement("UPDATE revList SET Status=?, CustID?, Message=?, Menu=?, LastUpdate=? WHERE rsvDate=? AND rsvTime=? AND Biyoshi=?AND CustID=? AND 【s】 "); ← 25行目のパラメタ付きSQLのWHERE句を修正
26:
27~46:(省略)[図5の27~46と同じ]
47:        int rsvStatus = rs.getInt("Status");
48:       String rsvCustID = rs.getInt("CustID");
49:       if (rsvStatus == RSV_KARI) {
50:           // 予約状況が空きであることを確認し、仮予約処理を行う
51:           (省略)[予約リストを適切に更新し、仮予約処理を行う]
52:        } else if (rsvStatus == RSV_KARI && ((nowDateTime - lastDateTime ) > TIMEOUT_KARI )) {
53:           // 既に仮予約状態であっても、TIMEOUT_KARI経過後なら、自分の仮予約に変更する
54:            psUp.setInt(1, RSV_KARI); psUp.setString(2, loginUserID); psUp.setString(3, "");
55:            psUp.setString(4, ""); psUp.setLong(5, nowDateTime);
56:            psUp.setString(6, rsvDate);
57:            psUp.setString(7, rsvTime); psUp.setString(8, rsvBiyoshi);
58:           psUp.setString(9, rsvCustID); 【t】 ; ← 25行目のWHERE句修正に伴う修正
59:           if (psUp.executeUpdate() == 【u】 ) { ← 更新した行数が戻り値になることに着目 + 異常の発生を検知する仕組み
60:                (省略)[図6の処理を引き継ぐために、選択した予約枠の情報をサーブレットのセッション情報に保存する]
61:                (省略)[画面3のHTML出力を行う]
62:            } else {
63:               (省略)[予約失敗の画面のHTML出力を行う]
64:            }
65:        } else {
66~71:(省略)[図5の64~69と同じ]
注記1 SQL例外を含め、例外処理については省略している。省略した部分で必要とするクラスに関わるimportの記述も省略している。
注記2 下線は、修正した部分を表す。
注記3(省略)[・・・]は、その位置に[]内に示す処理が記述されているが、省略していることを示す。

図9 修正されたサーブレット"WakuClick"終わり


 まず、今回の不具合が発生した原因は「αが更新した後にβが更新した」です。これを防ぐ仕組みを「排他制御」と呼びますが、データベースにおける排他制御には大きく二種類存在します。

1.楽観的ロック
データベース更新時に「更新対象のレコードが他のスレッドやジョブにより更新されていないことを確認する」方式です。他のスレッドやジョブが先に更新していたら、「処理中に他の処理によりレコードが更新されました」などのエラーメッセージを表示して処理を終了したりします。
実現方法は、更新SQLのWHERE句に最終更新日時や、バージョン、更新回数などを追加し、 更新件数が意図したとおりなら正常、意図しない値ならエラー とします。

2.悲観的ロック
更新対象となるレコードを予めロックしておいて、「他のスレッドやジョブが更新できないようにする」方式です。
実現方法は、Select句の末尾に「For Update」を追加することで、データベース製品の方でロックをかけてくれます。

今回の問題で採用しているのは、 更新件数が意図したとおりなら正常、意図しない値ならエラー から楽観的ロックです。これを踏まえて各記号に入る字句を見ていきましよう。

【s】:25: psUp = conn.prepareStatement("UPDATE revList SET Status=?, CustID?, Message=?, Menu=?, LastUpdate=? WHERE rsvDate=? AND rsvTime=? AND Biyoshi=?AND CustID=? AND 【s】 ");
  25行目のパラメタ付きSQLのWHERE句を修正 に相当する部分です。下線の修正内容として「AND CustID=?」が追加されています、これは解答のa郡のための布石と思われます。管理人は素直に楽観的ロックの説明にもでてきた「最終更新日時」を採用し「LastUpdate=?」としました。
【t】:58: psUp.setString(9, rsvCustID); 【t】 ;
  25行目のWHERE句修正に伴う修正 箇所です。ここでは、【s】でa郡、b郡どちらを選んだかによって解答が変わりますが、選んだ郡を担当する変数を設定すればよいです。管理人はb郡の最終更新日時を選んでいたので「psUp.setLong(10, lastDateTime);」としました。
【u】:59: if (psUp.executeUpdate() == 【u】 ) {
  更新した行数が戻り値になることに着目 異常の発生を検知する仕組み を担当する部分です。ここでは、更新件数が意図したものであれば正常、意図しないものであればエラーとするので、「1」が入ります。