- PR -

画面遷移中に値が書き換わる

1
投稿者投稿内容
はな
会議室デビュー日: 2006/10/10
投稿数: 5
投稿日時: 2007-07-09 19:27
いつも参考にさせていただいています。
struts1.1を利用して開発しています。

以下の流れで、レコードを新規作成、または既存のレコードを更新する処理があります。

--------------------------
URLにアクセス
| InputAction
v
画面1 データ入力
| ValidateAction
v
画面2 入力データ確認画面
| ConfirmAction
v
画面3 登録完了画面
--------------------------

新規作成と、更新は同じAction,jsp を共有しており、
"input_status"というパラメータで画面表示や、DBへの登録処理などを分岐しています。

問題は、画面1から画面3になる間に、
input_statusの値が変わるという現象が何度も発生していることです。

マルチスレッドが原因かと考えていますが、
各Actionで使用している input_status はインスタンス変数にしていないし、
画面間の値共有に使用している HttpSession はクライアント毎に異なるセッションを
持つと理解していたので、この仕組みで、なぜ値が書き換わる現象がおきるのかわかりません。

HttpSession は、スレッドセーフではないのか?と思い、
Firefox でタブを同時に「新規作成画面」と「更新画面」
を同時に開いたら、JSESSIONIDは同じ値であり、input_status は後に開いた画面に
書き換わっていました。

これが原因だと思ったら、一方、現象が発生した条件下では
同じクライアントPCで複数画面を開いていなかったと報告を受けています。

そこで、よろしければ教えてください。

1) HttpSession はスレッドセーフとはいえないのでしょうか。
別ユーザーの値に置き換わることはあるのでしょうか。
2) そもそも、
「新規作成と、更新は同じAction,jsp を共有しており、
"input_status"というパラメータで画面表示や、DBへの登録処理などを分岐しています。」
は、好ましくない形でしょうか。(別Action,jsp でわけたほうがいい?)
3) Firefox だと JSESSIONID は、一つのPCで共有されてしまうし、
ブラウザ間で値を共有せず、処理の流れの画面間の共有する値として
推薦する方法はありますか?
HttpSession? HttpRequestのset/get? hidden? Cookie? Jsessionid?


この会議室で、何度もマルチスレッド、スレッドセーフについて話題を
拝見しましたが、上記について腑に落ちなかったので質問させていただきました。
不明な点、認識の誤りがあればご指摘いただけるとうれしいです。

どうぞよろしくお願いします。



以下に、簡単にしたコードも記載します。

public class InputAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException,Exception
{
HttpSession session = request.getSession();
String input_status = "new"; // デフォルトは"new"
input_status = request.getParameter("input_status");

if(input_status.equals("new")){
新規レコード作成のための画面作成
}
else if(input_status.equals("update"))
{
既存レコード更新のための画面作成
}
}
session.setAttribute("input_status", input_status);
return mapping.findForward("success");
}
}

public class ValidateAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException,Exception
{
HttpSession session = request.getSession();
String input_status = (String)(session.getAttribute("input_status"));

if(input_status.equals("new")){
値検証
}
else if(input_status.equals("update"))
{
値検証
}
session.setAttribute("input_status", input_status);
return mapping.findForward("success");

}

}

public class ConfirmAction extends Action
{
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException,Exception
{
HttpSession session = request.getSession();
String input_status = (String)(session.getAttribute("input_status"));

if(input_status.equals("new")){
新規レコード作成(DB Create)
}
else if(input_status.equals("update"))
{
既存レコード更新(DB Update)
}
}
}
小僧
ぬし
会議室デビュー日: 2002/08/14
投稿数: 526
投稿日時: 2007-07-09 20:20
>input_statusの値が変わるという現象が何度も発生していることです。

どう変わるんでしょうか。nullなのか別の文字列が設定されるのか。

セッションはスレッドセーフではありますが、2つのフレームが同じアクション
にアクセスするようなフレームで区切られているような場合、FireFoxで試された
ような状態になるので、その場合はコーディングによっては意図しない値が設定
される場合があります。そういったページということでは無いでしょうか?。
リモートデバッグやデバッグログなどを仕込んでも、書き換わるタイミングが
分からないようでしたら、セッションリスナーを作って書き換わるタイミングを
見たりする方法もありますよ。

意外にありがちなのが、
  session.setAttribute("input_status", input_status);
の、設定する属性名の打ち間違いなんていうこともありますよ。簡易コードという
ことですので実際のコードでは、そう書かれているかもしれませんが、処理全体で
同一の識別子を使用する場合は、static final String などで、1文字打ち間違えていた
という、つまらないことが起こらないようにしましょう。



朝日奈ありす
大ベテラン
会議室デビュー日: 2007/05/02
投稿数: 189
お住まい・勤務地: 最北の地
投稿日時: 2007-07-09 21:09
タブでひらくなとだけいっておく
はな
会議室デビュー日: 2006/10/10
投稿数: 5
投稿日時: 2007-07-10 10:47
返信ありがとうございます。

>>> 小僧さん

>>input_statusの値が変わるという現象が何度も発生していることです。
>どう変わるんでしょうか。nullなのか別の文字列が設定されるのか。

後に開いた画面のinput_status に変わります。
つまり、最初に新規作成画面を開き、別にブラウザ(ブラウザの種類は同じ)をあげて更新画面を開いた場合、"update"になります。逆に、先に更新画面を開き、後に、別にブラウザをあげて新規画面を開くと、"new"になります。

>2つのフレームが同じアクションにアクセスするようなフレームで区切られているよう
>な場合、FireFoxで試された ような状態になるので、その場合はコーディングによって>は意図しない値が設定される場合があります。そういったページということでは無いで>しょうか?。

まさに、そういったページです。ということは、Firefoxのようなタブブラウザで、同時に新規、更新画面を開いていはいけないということが、制限事項になるんですね。ただ、今回の件では、

> これが原因だと思ったら、一方、現象が発生した条件下では
> 同じクライアントPCで複数画面を開いていなかったと報告を受けています。

と記載したように、同一PCで同じ画面を開いていなかったと報告を受けています。

>リモートデバッグやデバッグログなどを仕込んでも、書き換わるタイミングが
>分からないようでしたら、セッションリスナーを作って書き換わるタイミングを
>見たりする方法もありますよ。

デバッグログを仕込んでみたら、画面2で値が変わる場合もあれば、画面3で値が変わる場合もあり、発生条件が一定していませんでした。セッションリスナーは、試していないので調べてやってみたいと思います。ありがとうございます。

> 意外にありがちなのが、
>  session.setAttribute("input_status", input_status);
> の、設定する属性名の打ち間違いなんていうこともありますよ。

確認しましたが、うち間違いはありませんでした。

>>>>杏さん

確かにおっしゃるとおりで、ユーザーにもそう注意を促しているのですが、上記にもあるとおり、同一PCで同じ画面を開いていなかったと報告を受けています。
小僧
ぬし
会議室デビュー日: 2002/08/14
投稿数: 526
投稿日時: 2007-07-10 11:44
引用:

後に開いた画面のinput_status に変わります。
つまり、最初に新規作成画面を開き、別にブラウザ(ブラウザの種類は同じ)をあげて更新画面を開いた場合、"update"になります。逆に、先に更新画面を開き、後に、別にブラウザをあげて新規画面を開くと、"new"になります。


つまり、2つのブラウザは同一セッションを使っているということですね。
別のブラウザの上げ方って、開いているブラウザから「新しいウインドウ」などで
開いたりしてません?、javascriptのwindow.openなんかもそうですが。だとすると、
APサーバとしては二度アクセスされたのと同じ動作になるので、正しく動作している
ということですね。

引用:

まさに、そういったページです。ということは、Firefoxのようなタブブラウザで、同時に新規、更新画面を開いていはいけないということが、制限事項になるんですね。



フレーム分割は、タブブラウザで表示しているのと変わらない動作になるので、だめですね。セッションの同期化というよりも、この使い方にはあまりあっていないプログラム
構造だと思います。hiddenかリクエストパラメータの後ろ側に新規か更新かを判断する
パラメータを付けて、それに応じて処理を行う方が単純で楽だと思います。
それから、質問の最初に画面構成などを書いておくと話が早かったと思いますよ。

引用:

ただ、今回の件では、

> これが原因だと思ったら、一方、現象が発生した条件下では
> 同じクライアントPCで複数画面を開いていなかったと報告を受けています。

と記載したように、同一PCで同じ画面を開いていなかったと報告を受けています。



私はこの手のトラブルに出くわした場合、利用者の話半分、運用ログ半分で判断します。
はなさんの携わっているシステムは、ユーザのアクセス状況をトラッキングができないということが問題だと思います。ポイントポイントでのセッションID値の出力をログ出力
しておけば、クライアントの多重アクセスなどはすぐに判断できるようになります。
引用:


> 意外にありがちなのが、
>  session.setAttribute("input_status", input_status);
> の、設定する属性名の打ち間違いなんていうこともありますよ。

確認しましたが、うち間違いはありませんでした。




ついでに、文字列比較を行い場合は
コード:
 static final String C_COMMAND = "new";
                          .
                          .
                          .
 if(C_COMMAND.equals(input_status)){



の方がNullPointerExceptionが発生しないので良いですよ。



はな
会議室デビュー日: 2006/10/10
投稿数: 5
投稿日時: 2007-07-10 18:47
小僧さん、度々返信ありがとうございます!

引用:

私はこの手のトラブルに出くわした場合、利用者の話半分、運用ログ半分で判断します。
はなさんの携わっているシステムは、ユーザのアクセス状況をトラッキングができないということが問題だと思います。ポイントポイントでのセッションID値の出力をログ出力しておけば、クライアントの多重アクセスなどはすぐに判断できるようになります。



ご指摘のとおりです。利用者のアクセス状況の運用ログが乏しいことが問題と認識していながら対応が遅れていました。これを機会にログ出力をいくつかの場所に埋め込むことにしました。

引用:


ついでに、文字列比較を行い場合は
コード:
 static final String C_COMMAND = "new";
                          .
                          .
                          .
 if(C_COMMAND.equals(input_status)){



の方がNullPointerExceptionが発生しないので良いですよ。




こちらもご指摘ありがとうございます!確かに、NullPointerException が発生していました。。。早速コードを変更したいと思います。

ひとまず、取得する運用ログを見ながら問題箇所を明確にして、

引用:

hiddenかリクエストパラメータの後ろ側に新規か更新かを判断する
パラメータを付けて


といった対応を検討したいと思います。
ありがとうございました。
小僧
ぬし
会議室デビュー日: 2002/08/14
投稿数: 526
投稿日時: 2007-07-10 21:28
引用:

ご指摘のとおりです。利用者のアクセス状況の運用ログが乏しいことが問題と認識していながら対応が遅れていました。これを機会にログ出力をいくつかの場所に埋め込むことにしました。



業務処理で実行した処理内容をログに出力する場合は、あらかじめプログラムの
適所(DB処理のコミット直後など)に書くことを決めておくと良いのですが、
実運用に入っちゃってから、セッション絡みの問題が出てしまった場合は、
元のソースコードに手を加えるとデグレの原因にもなるので、セッションIDや
リクエストヘッダなどをログ出力するサーブレットフィルタを作っておくと、
web.xmlの設定変更だけでクライアント情報をログ出力する機能を追加すること
ができますよ。これを機に一度作ってみてはいかがでしょうか、あっという間に
できますから。
1

スキルアップ/キャリアアップ(JOB@IT)