質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JSP

JSP(Java Server Pages)とは、ウェブアプリケーションの表示レイヤーに使われるサーバーサイドの技術のことです。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

servlet

Servletとは、Webページの動的な生成やデータ処理などをサーバ上で実行するために、Javaで作成されたプログラムです。 ショッピングサイトやオンラインバンキングといった、動的なウェブサイトの構築に用いられています。

Q&A

解決済

1回答

8991閲覧

別ユーザーの同時アクセス時に対応について

n000n00

総合スコア25

JSP

JSP(Java Server Pages)とは、ウェブアプリケーションの表示レイヤーに使われるサーバーサイドの技術のことです。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

servlet

Servletとは、Webページの動的な生成やデータ処理などをサーバ上で実行するために、Javaで作成されたプログラムです。 ショッピングサイトやオンラインバンキングといった、動的なウェブサイトの構築に用いられています。

0グッド

0クリップ

投稿2017/01/21 10:54

編集2017/01/24 06:24

◆追記 1/24
UpdateUserCheck を作成しました。

ページを離れた事の判定ですが、
現在は、ListServletに戻った時点で、mapからremoveする処理を追加しました。

恐らく、これで対応できると思いますが、
一点、編集中の画面をブラウザの閉じるボタンを実施したときの対応について悩んでおります。

引き続き、知恵を貸していただけないでしょうか。
よろしくお願いします。

◆追記 1/23
Mapを利用した、ソースを書いてみました。UpdateUserCheck_N
(まだ、動かしてません)

引き続き、良い方法がないか知恵を貸していただきたいです。

◆追記
UpdateUserCheckを編集しました。
最初にページに入ったログインユーザIDとページネームをtmpに格納して、比較しようと考えています。

途中の段階ですが、引き続き知恵を貸していただけないでしょうか。


現在、ServletとJSPで掲示板を作成しています。

◆実現したいこと
ログイン後に既存ページを更新中に、別のユーザーから更新ページにアクセスがあった場合に、更新ページに入らせない。
ユーザーIDはsessionで保持。
一覧ページから編集したい項目の名前を押して、編集を押すと、パラメータをUsercheckServletに渡し、同一ユーザーかチェックし、同一ユーザーであれば、更新ページ(update.jsp)に飛ぶという流れにしようと思っています。

同時アクセス時の編集したい項目の名前(name)とユーザーID(id)を比較するロジックに関して、知恵を拝借したいです.

よろしくお願いします。

※UsercheckServletは編集中のものです。

<h1>${wikiPage.name}</h1> ${wikiPage.formatedContent} <hr> <c:if test="${login == 'OK' && wikiPage.name != 'welcome'}"> <a href="/wiki/UpdateUserCheck?id=${account.id}&name=${wikiPage.name}&content=${wikiPage.content}">このページを更新</a> </c:if>

JSP

1<h1>${param.name}の編集</h1> 2<form action="/wiki/update"> 3 <input type="hidden" name="cmd" value="update"> 4 <input type="hidden" name="name" value="${param.name}"> 5 <textarea rows="15" cols="60" name="content">${param.content}</textarea> 6 ※200文字以内 7 <br> 8 <input type="submit" value="更新ˆ"> 9 <input type="submit" value="削除" onclick="cmd.value='delete'"> 10 <input type="button" value="キャンセル" onclick="location.href='list?name=${param.name}&id=${param.id}'"> 11</form> 12 13``` 14```Servlet 15package wiki; 16 17import java.io.IOException; 18import java.util.List; 19import java.util.Map; 20 21import javax.servlet.ServletContext; 22import javax.servlet.ServletException; 23import javax.servlet.http.HttpServlet; 24import javax.servlet.http.HttpServletRequest; 25import javax.servlet.http.HttpServletResponse; 26 27public class ListServlet extends HttpServlet { 28 29 @Override 30 public void doGet(HttpServletRequest request, HttpServletResponse response) 31 throws ServletException, IOException { 32 33 ServletContext sc = getServletContext(); // アプリケーションスコープの取得 34 String name = request.getParameter("name"); 35 36 /* リストページアクセス時に編集ページにアクセスの履歴があれば、処理を実施 */ 37 if (sc.getAttribute("tmpMap") != null) { 38 int id = 0; 39 if (request.getParameter("id") == null || request.getParameter("id").isEmpty()) { 40 id = -1; 41 } else { 42 id = Integer.parseInt(request.getParameter("id")); 43 } 44 45 Map<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); 46 if (map.containsKey(name)) { 47 if (map.get(name) == id) { 48 map.remove(name); 49 sc.setAttribute("tmpMap", map); 50 } 51 } 52 } 53 54 try { 55 List<WikiBean> list = WikiPageDAO.getInstance().findAll(); 56 request.setAttribute("list", list); 57 } catch (Exception e) { 58 throw new ServletException(e); 59 } 60 61 request.getRequestDispatcher("/wikiView/list.jsp").forward(request, response); 62 } 63 64 /* 戻り値の型に合わせてキャスト */ 65 @SuppressWarnings("unchecked") 66 public static <T> T autoCast(Object obj) { 67 T castedObject = (T) obj; 68 return castedObject; 69 } 70} 71``` 72 73```Servlet 74package wiki; 75 76import java.io.IOException; 77import java.util.HashMap; 78 79import javax.servlet.ServletContext; 80import javax.servlet.ServletException; 81import javax.servlet.http.HttpServlet; 82import javax.servlet.http.HttpServletRequest; 83import javax.servlet.http.HttpServletResponse; 84import javax.servlet.http.HttpSession; 85 86import Account.AccountBean; 87 88public class UpdateUserCheck extends HttpServlet { 89 90 @Override 91 public synchronized void doGet(HttpServletRequest request, HttpServletResponse response) 92 throws ServletException, IOException { 93 94 HttpSession session = request.getSession(false); 95 ServletContext sc = getServletContext(); // アプリケーションスコープの取得 96 97 String name = request.getParameter("name"); // 編集中のページ名を取得 98 AccountBean account = (AccountBean) session.getAttribute("account"); 99 100 /* アプリケーションスコープがnullであれば、新しくMapを取得する */ 101 HashMap<String, Integer> map = null; 102 if (sc.getAttribute("tmpMap") == null) { 103 map = new HashMap<String, Integer>(); 104 } else { 105 map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト 106 } 107 108 String message = null; 109 if (map.containsKey(name)) { 110 if (map.get(name) != account.getId()) { 111 message = "他のユーザーにて編集中です"; 112 request.setAttribute("message", message); 113 request.getRequestDispatcher("list").forward(request, response); 114 } else { 115 map.put(name, account.getId()); 116 sc.setAttribute("tmpMap", map); 117 request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response); 118 } 119 } else { 120 map.put(name, account.getId()); 121 sc.setAttribute("tmpMap", map); 122 request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response); 123 } 124 } 125 126 /* 戻り値の型に合わせてキャスト */ 127 @SuppressWarnings("unchecked") 128 public static <T> T autoCast(Object obj) { 129 T castedObject = (T) obj; 130 return castedObject; 131 } 132} 133```

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

現在編集中のユーザの情報(tmpId, tmpName)はリクエスト、セッションを越えて共有できる必要がありますので、アプリケーションスコープの変数として保持する必要があります。

Servlet スコープとは?のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。

ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。

また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。


以下に排他制御について書いてみますが、手元に servlet をデバッグする環境がないので、すべて未検証(コンパイルエラーがでるかもしれない)であることをご了承ください。

いろいろ、ググッて調べたのですが、「もし、未登録であれば登録する」というロジックで満足に排他制御できているサンプルが見つかりませんでした。ServletContext は複数のスレッド(ユーザからのリクエスト)から同時にアクセスされるので、「もし、未登録であれば」という if 文を2つのスレッドが同時に実行すると、両方のスレッドが「登録する」処理を実行してしまうので、バグってしまいます。また、この手のバグはなかなか顕在化しないので、たちが悪いです。

まず、ServletContext にマップを登録する処理ですが、初期化イベントのリスナーで実行しておくべきです。アプリケーションが動作する前に実行されるので、排他制御の必要がありません。

java

1import javax.servlet.ServletContextEvent; 2import javax.servlet.ServletContextListener; 3import java.util.concurrent.ConcurrentHashMap; 4@javax.servlet.annotation.WebListener 5public class WebListener implements ServletContextListener { 6 @Override 7 public void contextDestroyed(ServletContextEvent paramServletContextEvent) {} 8 @Override 9 public void contextInitialized(ServletContextEvent paramServletContextEvent) { 10 paramServletContextEvent.getServletContext().setAttribute("tmpMap", 11 new ConcurrentHashMap<String, Integer>()); 12 } 13}

こうしておいて、サーブレット側では ConcurrentHashMap の putIfAbsent を使って排他制御します。

java

1package wiki; 2 3import java.io.IOException; 4import java.util.concurrent.ConcurrentHashMap; 5 6import javax.servlet.ServletContext; 7import javax.servlet.ServletException; 8import javax.servlet.http.HttpServlet; 9import javax.servlet.http.HttpServletRequest; 10import javax.servlet.http.HttpServletResponse; 11import javax.servlet.http.HttpSession; 12 13import Account.AccountBean; 14 15public class UpdateUserCheck extends HttpServlet { 16 17 @Override 18 public synchronized void doGet(HttpServletRequest request, HttpServletResponse response) 19 throws ServletException, IOException { 20 21 HttpSession session = request.getSession(false); 22 ServletContext sc = getServletContext(); // アプリケーションスコープの取得 23 24 String name = request.getParameter("name"); // 編集中のページ名を取得 25 AccountBean account = (AccountBean) session.getAttribute("account"); 26 27 /* 初期化イベントリスナーで登録したMapを取得する */ 28 ConcurrentHashMap<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト 29 30 String message = null; 31 final Integer currentUid = autoCast(map.putIfAbsent(name, account.getId())); 32 if (currentUid != null) { 33 if (! currentUid.equals(account.getId())) { 34 message = "他のユーザーにて編集中です"; 35 request.setAttribute("message", message); 36 request.getRequestDispatcher("list").forward(request, response); 37 } else { 38 request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response); 39 } 40 } else { 41 request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response); 42 } 43 } 44 45 /* 戻り値の型に合わせてキャスト */ 46 @SuppressWarnings("unchecked") 47 public static <T> T autoCast(Object obj) { 48 T castedObject = (T) obj; 49 return castedObject; 50 } 51}

また、ユーザが編集を終了するときに map から削除するようにしておかないと、上記コードは動作しません。さらに編集状態のまま、ログアウト時や放置されてセッションがタイムアウトした時も編集状態を開放する処理が必要であると思われます。

投稿2017/01/22 01:23

編集2017/01/25 10:45
mit0223

総合スコア3401

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

n000n00

2017/01/22 11:53

回答ありがとうございます。 ご指導頂いたように、applicationスコープを利用して、ifでユーザーを特定するソースは書くことができました。ありがとうございます。 ご指摘頂いたように、掲示板にはページが複数あります。 教えて頂いたようにHashMapを利用して、複数ページに対応しようと考えていますが、あまり利用したことがないため、考えがまとまらない状況です。 何か、このような流れで等の例を、よろしければご教授いただけないでしょうか。
n000n00

2017/01/27 05:09

回答ありがとうございます。 リスナーについては、私が読んでるServletのテキストには載っていなくて、 ググって勉強をしている段階です。 実装についてまだ理解ができていないですが、参考にさせていただきます。 ご丁寧に対応していただき、ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問