■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2006年10月22日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.023

                                セルゲイ・ランダウ
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


========================================================
◆ 01.データベース
========================================================

それでは、JDBCを説明していきます。

前回のJavaプログラム例を再度提示します。

---------------------------------------------------
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DbTest {
   public static void main(String[] args) {
      {
         try {
            Class.forName("org.h2.Driver").newInstance();
            Properties props = new Properties();
            props.put("user", "sa");
            props.put("password", "");
            Connection conn = DriverManager.getConnection
            ("jdbc:h2:tcp://localhost:9092/test", props);
            conn.setAutoCommit(false);
            Statement st = conn.createStatement();
            st.execute("INSERT INTO hotel  VALUES (1, '有名ホテル', '市川市なんとか町100-100-100')");
            st.execute("INSERT INTO hotel  VALUES (2, '無名ホテル', '市川市かんとか町200-200-200')");
            st.execute("UPDATE hotel SET address = '船橋市かんとか町200-200-200'  WHERE num = 2");
            st.close();
            conn.commit();
            conn.close();
         }
         catch (SQLException exception) {
            exception.printStackTrace();
         }
         catch (Exception otherException) {
            otherException.printStackTrace();
         }
      }
   }
}
---------------------------------------------------

まず、org.h2.Driverは、org.h2というパッケージにはいっているDriverと
いうクラスを意味しますが、実はこれがH2 Database EngineのJDBCドライ
バーです。

そして13行目

Class.forName("org.h2.Driver").newInstance();

は、JDBCドライバーのインスタンスを生成しています。つまり

new org.h2.Driver();

と同じことをしています。

細かい話をすると、Javaは、

new org.h2.Driver();

という文を実行する際には、まずDriverクラスがメモリー上に既に存在して
いるかどうか確認し、存在していなければクラス・ローダーというソフトを
使ってDriverクラスを探して来てメモリー上に取り込み(専門用語では
「ロードする」という)ます。次にロードされたクラスに対してインスタンス
を生成します。

この「クラスをロードする」という操作は、普通はJVMによって自動的に行わ
れているのですが、

Class.forName("org.h2.Driver")

と書くことによって、明示的に行うことができます。

Classというのは文字通りクラスを管理するクラスです。

forName()は、Classのスタティック・メソッド(staticが指定されたメソッドの
ことで、インスタンスを生成しなくても実行できる)で、引数に指定されたクラ
ス(ここではorg.h2.Driver)をクラス・ローダーでロードし、そのクラスを表現
するClassオブジェクトを戻り値として返します。

そのClassオブジェクトのnewInstance()メソッドを実行すると、そのクラス
(ここではorg.h2.Driver)のインスタンスが生成されます。

結局のところ、これは

new org.h2.Driver();

とやっていることは同じですが、Class.forName()を使うと何がありがたいかと
いうと、これを使えば、JDBCドライバーのクラス名をプログラムの中に固定で
指定する必要がないということです。

たとえば、

String driverName;
//....(中略)....
Class.forName(driverName);

というように引数に変数名(ここではdriverName)を指定しておき、プログラム
の実行時に外部から読み込んだ値を変数(driverName)に設定するようにすると、
実行時にドライバーのクラス名を変更することができます。

通常は、外部に設定値を置くためのファイルを用意し、そこから読み込むように
します(詳細は後述)。

こうしておけば、のちにMySQLやPostgreSQLなどの他のRDBMSに変更する場合でも
プログラムを変更する必要がありません。

new org.h2.Driver();

としてしまったのでは、このような芸当は使えません。

もう一つあります。
実は、JDBCドライバーのクラスは、クラス・ローダーによってロードされると、
自動的にインスタンスを生成するように作られています。

したがって、明示的にインスタンスを生成する必要さえないのです。
つまり、

new org.h2.Driver();

も必要なければ、13行目

Class.forName("org.h2.Driver").newInstance();

のようにnewInstance()メソッドを呼び出す必要もなく、

Class.forName("org.h2.Driver");

とするだけでこと足りてしまうのです。

13行目でnewInstance()を使ったのはnew org.h2.Driver()と対比して説明する
ためであり、通常のプログラムでは

Class.forName("org.h2.Driver");

という文にします。

ついでに、もう少し説明すると、JDBCドライバーのクラスはクラス・ローダー
によってロードされると、インスタンスを生成したあと、自分でDriverManager
に登録する(そのインスタンスの存在をDriverManagerに知らせる)ように作ら
れています。これについては、後のDriverManagerの説明のところで再度触れます。

次に14行目

Properties props = new Properties();

の部分を見てみましょう。

Propertiesというのは何らかの設定値を管理するためのクラスで、通常は、
「user=ユーザー名の設定値」というような形式のデータ行を含む設定用ファ
イルを用意しておき、そこから設定値を読みこんで使用するためによく使われ
ます(後述)。

実用的なプログラムでは、設定用のファイルを用意してそこから読み込むよう
にすべきですが、ここでは
15〜16行目

props.put("user", "sa");
props.put("password", "");

のようにプログラム上で設定値を設定しています。put()は、キーに値を設定
するメソッドで、たとえばput("user", "sa")は、「user」という名前のキー
に「sa」という値を設定します。

このsaというのは、H2 Database Engineにデフォルトで用意されているユーザー名
です。

このsaというユーザー名はデフォルトではパスワードが設定されていないので、
16行目のようにpasswordに空文字("")を設定しています。

(なお、put()はPropertiesのスーパークラスのメソッドです。代わりに
Properties自身のメソッドsetProperty()を使っても同じです。)

続いて17〜18行目

Connection conn = DriverManager.getConnection("jdbc:h2:tcp://localhost:9092/test", props);

の部分を見てみましょう。

このDriverManagerというのは文字通りJDBCドライバー(driver)を管理(manage)
するためのクラスで、主にJDBCドライバーを通してデータベースに接続するとき
に使用します。

そしてgetConnection()というメソッド(これもスタティック・メソッド)がまさ
にデータベースに接続するためのメソッドです。

getConnection()の一番目の引数は、データベースのありかを示すURLです。

URLというと皆さんは「http://www.flsi.co.jp/」というように「http」という
プロトコルを指定したものになじみがあるでしょうけれど、JDBCで使用するURL
ではプロトコルは18行目の

"jdbc:h2:tcp://localhost:9092/test"

のように「jdbc」になります。
(jdbcプロトコルを用いたURLを特に「JDBC URL」と呼ぶ。)

そしてその後ろの「h2」以下はRDBMSによって異なりますので、RDBMSのマニュ
アル(取扱説明書)にしたがって正しく指定する必要があります。しかも、
同じRDBMSを使う場合でも接続のタイプによって異なります(後述)。
(接続のタイプによって異なるJDBCドライバーを使用するRDBMSもあります。)

(とりあえず、RDBMSにH2 Database Engineを採用し、RDBMSおよびそのデータ
ベースがアプリケーションと同じパソコンに入っている場合は18行目のJDBC URL
で接続することができます(もっと簡単なJDBC URL指定("jdbc:h2:test")も
可能です)。
また、インターネットやLANなどで接続された別のコンピューターに入っている
データベースに接続する場合は、「localhost」の代わりにIPアドレスもしくは
ドメイン名を入れることになります。)

そして、18行目の「jdbc:」の後ろの「h2」はH2 Database EngineのJDBCドライ
バーを使用すること(したがって、RDBMSとしてはH2 Database Engineを使用する
こと)を意味し、その後ろの「tcp://localhost:9092/」はアプリケーション・プ
ログラムと同じコンピューター内のポート番号9092のプログラム(RDBMS)にTCPプ
ロトコルで接続すること(H2 Database EngineのRDBMSはポート番号9092を使用す
ることになっています)を意味し、その後ろの「test」はtestという名前のデータ
ベースに接続することを意味します。(ちなみに、先日使用したH2 Consoleの場合
はhttpプロトコルで接続するのでポート番号が8082というように、異なります。)

なお、データベースへはJDBCドライバーを通して接続しますから、DriverManager
はJDBCドライバーの存在を認識していなければならないわけですが、これは先ほど
のClass.forName()でJDBCドライバーのクラスがロードされたときにJDBCドライバー
自身がDriverManagerに登録することで行われています。

DriverManagerは18行目の「jdbc:」の後ろに「h2」と書かれているのを見つけると、
h2の名で登録されているJDBCドライバーのクラス(それがorg.h2.Driver)を呼び
出すのです。


まだお話していませんでしたが、データベースのテーブルを作成する前にはその
テーブルを入れるための入れ物を作成しておく必要があります。
そしてその入れ物自体が「データベース」と呼ばれています。

通常のRDBMSでは、この(入れ物としての)データベースの作成は

CREATE DATABASE データベース名

というSQL文によって実行されるのですが、H2 Database Engineの場合は、この
SQL文の代わりに18行目のようにJDBC URLの中で指定したデータベース名に対し
て、そのデータベースがまだ存在していないときは自動的に作成し、存在してい
るときはそれをそのまま使う、という方式になっています。

そして18行目で指定したtestというデータベースは実は、先日H2 Consoleを最初
に起動したときに自動的に作成されています。

H2 Consoleの最初のLoginの画面を再度確認してみてください。「JDBC URL」の
項目にtestというデータベース名が入っていることがわかりますね。これがデフォ
ルトで作成されるデータベースです。

今はプログラムのテスト段階なのでこれでいいのですが、本番のシステムを稼動
するときにデータベース名がtestというのでは、おかしいですね。
本番稼動のシステム用には別の名前のデータベースを作成して使用する必要があり
ます。
一般には、データベースを(テスト用、本番用など)いくつか作成して使い分けま
す。


次にgetConnection()メソッドの2番目の引数を見てみましょう。

ここにはさきほど14行目〜16行目で用意したProperitesクラスのインスタンス
(ここではpropsという変数名)が指定されていますね。

データベースに接続するときにはRDBMSがユーザー名とパスワードを要求し、正し
い値を入力しないと接続を拒否するようになっていますので、Properitesクラスの
インスタンスを指定することによってユーザー名とパスワードをRDBMSに渡している
のです。

ユーザー名とパスワードを渡すためには、他に

Connection conn = DriverManager.getConnection("jdbc:h2:tcp://localhost:9092/test", "sa", "");

という形式もあります。この場合はPropertiesクラスを使う必要がなく、ユーザー名
とパスワードを直接getConnection()メソッドに指定することになります。

いずれにしても、JDBC URLやユーザー名やパスワードが文字列(String型)で指定
されていることに注目してください。

これらは、さきほどClass.forName()で指定したクラス名と同じようにString型の
変数に置き換えて、実行時に外部から値を読み込むことが可能です。

DriverManagerのgetConnection()メソッドは、Connection型のインスタンスを
戻り値として返しますので、17行目では戻り値をConnection型の変数connに代入
しています。

このConnectionは、データベースへの接続を表現するインターフェイスです。

DriverManagerのgetConnection()メソッドが返すConnection型のインスタンスは
データベースへの接続の情報を保持し、データベースと情報のやり取りをする際
に使用されます。

次に19行目

conn.setAutoCommit(false);

を見てみましょう。

RDBMSには、自動コミット(autocommit)というモードの設定ができ、このモード
がオンの状態になっているとSQL文を実行するたびに毎回コミットが行われます。

ところが、通常のアプリケーションでは、一つのSQL文を実行する際には関連する
いくつかの他のSQL文もあわせて実行しなければならないことが多く、毎回コミッ
トされたのでは問題が生じます。

たとえば、ある人が銀行の口座Aから口座Bに預金を移し替えようとしたとします。
そのとき、データベース処理をするプログラムでは、

(1) 口座Aの該当する金額データを減少させるためのSQL文を実行した後、
(2) 口座Bの該当する金額データを同額分増加させるためのSQL文を実行しようとします。

ところが、なんらかの障害が発生して(2)の処理に失敗したとすると、自動コミット
がオンの状態では、口座Bの該当する金額データは増加せず、口座Aの該当する金額
データが減少したままになってしまいます。

こうような処理のしかたでは、この人は損をしてしまいますので、通常は自動コ
ミットをオンにしてはいけないのです。

なお、上の(1)と(2)のように必ずいっしょに処理しなければならない一連の処理を
まとめて「トランザクション(transaction)」と呼びます。(単純にデータの読み
書き処理をトランザクションと呼ぶ人もいるので要注意ですが。)

一般にトランザクションは不可分(トランザクションを分割して、その中の一部の
処理だけを実行して残りは処理しないということはできない)であり、この「不可分」
のことを「アトミック(atomic = [形容詞]分割不可能の)」と呼ぶことがあります。
英語の専門書を読むときに「atomic」を「原子的」などと訳したりしないように注意
しましょう。

通常は自動コミットはオンではなく、オフにしておき、(2)で障害が発生したら、(1)
の処理の前の状態に戻す(ロールバックする)ようにします。そして、(1)の処理が
成功してもコミットせず、(2)の処理まで成功したら、初めてコミットをするのです。

そこで、19行目ではsetAutoCommit(false)というメソッド呼び出しを行っていますが、
このメソッドはRDBMSに対して自動コミットのモードを設定するためのメソッドで、
引数のfalseは自動コミットをオフにすることを意味します(自動コミットをオンに
したいときはfalseの代わりにtrueを指定します)。


では、次に20行目

Statement st = conn.createStatement();

に進みましょう。

conn(Connection型のインスタンス)は、RDBMSにSQL文を送り込むためのSQL文を表現
するオブジェクトを生成してくれます。それがcreateStatement()メソッドです。

createStatement()メソッドは戻り値としてStatement型(これもインターフェース)
のインスタンスを返します。

ここではそのインスタンスをstという変数に代入していますが、このインスタンスを
使って、SQL文を送り込むことができるのです。

21〜23行目

st.execute("INSERT INTO hotel  VALUES (1, '有名ホテル', '市川市なんとか町100-100-100')");
st.execute("INSERT INTO hotel  VALUES (2, '無名ホテル', '市川市かんとか町200-200-200')");
st.execute("UPDATE hotel SET address = '船橋市かんとか町200-200-200'  WHERE num = 2");

のexecute()メソッドがSQL文をRDBMSに送り込むこむためのメソッドです。引数に
SQL文をまるごと指定しているのがわかりますね。

でもこのように毎回毎回同じようなSQL文を書くのは面倒くさいですね。
実はここら辺の面倒くさいところは簡単化できます。その方法は後述します。

次に、24行目の

st.close();

はSQL文をすべて送り終わってStatementのインスタンスがいらなくなったときの
後始末処理です。
これで、メモリー上の不要になった資源が解放されます。

その次の25行目

conn.commit();

は見てわかる通り、データベースに対してコミットを行っています。自動コミット
をオフにしているので、このようにトランザクションの終わりに明示的にコミット
をするのです。

26行目

conn.close();

はConnectionのインスタンスがいらなくなったときの後始末処理です。
これで、メモリー上の不要になった資源が解放されます。

その下の

catch (SQLException exception) {
   exception.printStackTrace();
}
catch (Exception otherException) {
   otherException.printStackTrace();
}

は以前説明した例外処理ですね。例外(Exception)のうちSQLExceptionを先に
捕らえて(exceptionという変数に渡して)処理し、そこで捕らえられなかった
例外、つまりSQLException以外のExceptionはその下で捕らえて(otherException
という変数に渡して)います。
Exceptionはすべての例外クラスのスーパークラスなので、ここでは残りのすべて
の例外が捕らえられます。

SQLExceptionは、SQL関連のメソッドが発する可能性のある例外で、具体的には
17行目〜26行目でデータベースにアクセスできなかったときに発生します。例外
の詳しい情報は、変数exceptionに代入されたSQLExceptionのインスタンスの中に
あるデータを取り出して調べることができます。

29行目と32行目のprintStackTrace()メソッドは例外のインスタンスが持っている
情報を出力するメソッドで、(デフォルトでは)Eclipseで実行しているときは
コンソールに、コマンド・プロンプトで実行しているときはその画面に出力され
ます。

しかし、本格的なアプリケーションでは(プロは)、このprintStackTrace()は
使わず、代わりにLog4jというのを使うのが普通です(後述)。



今回はひとまず、ここで終わりにしますが、前回の演習問題は、やってみたで
しょうか。まだやっていない人は、やってみてください。

なお、プログラムの実行の前にはあらかじめRDBMSが起動している必要があり
ます。
一番簡単なRDBMSの起動方法は、一度H2 Consoleを立ち上げておくことです。

もし、プログラムの中で起動したい場合は、プログラムの中でデータベースに
アクセスする前に

String[] param = new String[]{"-ssl", "true"};
Server server = Server.createTcpServer(param);
server.start();

という文を挿入します。
また、最後に

server.stop();

という文で後始末をします。

(続く)



========================================================
◆ 02.文法解説 [演算子]
========================================================

[算術用の二項演算子]
足し算や引き算などの算術用の二項演算子には以下のものがあり
ます。

演算子  機能
------  ---------------------------
+    足し算
-    引き算
*    掛け算
/    割り算
%    割り算をして計算結果として余りを返します。

(続く)



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      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) 2006 Future Lifestyle Inc. 不許無断複製