■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年05月24日

    Java総合講座 - 初心者から達人へのパスポート
                  vol.154

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.SOAPのアプリケーション(Webサービス)
========================================================


データベースへのアクセスは

(1) DbManager.connect()の実行
(2) テーブルへのアクセス
(3) DbManager.disconnect()の実行

という手順で行っていますので、HotelClientを2回実行した場合は、二回目
のDbManager.connect()の実行の前にはDbManager.disconnect()が実行され
ていますが、一回目のDbManager.connect()の実行の前には(つまり初め
てのDbManager.connect()の実行の前には)DbManager.disconnect()は
実行されていないことになります。

一回目のHotelClientの実行のときには、DbManagerのconnect()メソッド
が「Connection has been gotten.」というメッセージをログに書き出し
ているのに、二回目以降のHotelClientの実行のときには、同メッセージ
をログに書き出していないということは、このdisconnect()メソッドの実行
の有無が関係していると考えられます。

そこで、DbManagerのdisconnect()メソッドのソース・コードをよく見ると、

public static void disconnect() {
   logger.info("Start ...............");
   try {
      conn.close();
   } catch(SQLException e) {
   ・
   ・
   ・
   }
   finally {
      logger.info("End ...............");
   }
}

というようにconn(Connectionオブジェクト)のcloseだけを実行してconn変数
をnullにするようなことは行っていないので、connにはConnectionオブジェクト
が代入されている状態のままになります。
そうすると、その後connect()メソッドを実行するときには、connect()メソッド
の中の

if (conn == null) {
   ・
   ・
      conn = ds.getConnection();
      if(conn == null) throw new HotelDbException("Error: Connection does not exist!");
      else logger.info("Connection has been gotten.");
   ・
   ・
}

のブロックは実行されない訳です。
(そのため「Connection has been gotten.」というメッセージはログに書き出されない。)

そうすると、connect()メソッドを実行してもconnにはいっているConnectionオブ
ジェクトはcloseされた状態のままなので、データベースへのアクセスはできない
のです。
(そのため、HotelClientの二回目以降の実行で、ログに「Connection is closed.」
というSQLExceptionが記録される。)


したがって、connがnullでない場合でも、クローズされている状態であれば
conn = ds.getConnection();
を実行するようにconnect()メソッドを修正する必要があることがわかります。


では、DbManager.javaのソース・コードを以下のように修正してみましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.webservice.hotel.db;

import java.sql.SQLException;
import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.log4j.Logger;

public class DbManager {
   protected static Connection conn = null;
   private static Logger logger = Logger.getLogger(DbManager.class);

   public static void connect() throws HotelDbException {
      logger.info("Start ...............");
      try{
         if (conn == null || conn.isClosed()) {
            Context initCtx = new InitialContext();
            if(initCtx == null) throw new HotelDbException("Error: InitialContext could not be generated!");
            DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/HOTELDB");
            if (ds != null) {
               conn = ds.getConnection();
               if(conn == null) throw new HotelDbException("Error: Connection does not exist!");
               else logger.info("Connection has been gotten.");
            }
            else {
               throw new HotelDbException("Error: DataSource does not exist!");
            }
         }
      } catch (NamingException e) {
         logger.error(e, e);
         throw new HotelDbException("Error: Connect() failed!", e);
      } catch (SQLException e) {
         logger.error(e, e);
         throw new HotelDbException("Error: Connect() failed!", e);
      } catch (HotelDbException e) {
         logger.error(e, e);
         throw new HotelDbException("Error: Connect() failed!", e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
   }

   public static void disconnect() {
      logger.info("Start ...............");
      try {
         conn.close();
      } catch(SQLException e) {
         logger.error(e, e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         logger.info("End ...............");
      }
   }

}
--------------------------------------------------------

どこを修正したのか、わかりますね。修正した行は

if (conn == null || conn.isClosed()) {

の行です。ここで「||」は以前お話したように「または」を意味する演算子です。
ちなみに、このようなif文だと、conn == nullがtrueだと、||の右側の式(conn.isClosed())
は実行されません。したがって、ここでNullPointerExceptionが発生することは
ありません。conn == nullがfalseのときにのみconn.isClosed()が実行されて、その
結果がtrueであるかどうかが判定されます。


では、修正が終わったら、保管して、再度Antでデプロイを行っておきましょう。

そして、HotelClientを何度も実行してみて下さい。

今度は、正常に実行されますね。めでたし、めでたし・・・というのは、まだ早い
です。

もう一つ注意しておかなければならないことがあります。


このAxisのアプリケーションはTomcatのアプリケーションであり、マルチ・スレッド
で稼動するということを思い出してください。
つまり、同時に複数のクライアントから要求がくると、それぞれのクライアントごと
に別々のスレッドで実行されることになります。

マルチ・スレッドで実行されるアプリケーションは、同一の資源が同時に複数のスレッド
からアクセスされる可能性があるということに注意しなければなりません。

今回は、DBManagerの各メソッドやconn変数(Connectionオブジェクト)はstatic指定
にしておいた(わざとstatic指定にしておいた)ので、同じ変数が複数のスレッドから
同時にアクセスされる可能性があります。

そうすると、たとえば、あるスレッド(Aとする)でconnを使ってデータベースにアクセス
しようとしていたときに別のスレッドでconnをcloseしていた、ということも起こりえる
訳で、そうするとスレッドAではデータベースにアクセスできずエラーを起し
てしまいます。


(次回に続く)


では、今日はここまでにします。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2009 Future Lifestyle Inc. 不許無断複製