■開発研究記
TOP >  開発研究記一覧 > JDBCサンプル
2016/11/07 JDBCサンプル
 JavaでDBを使用するプログラムを書くのであれば、JDBCを使うのが基本であると思われる。DBといってもOracle、SQL Server、PostgreSQL、MySQL……等と色々ある。DBによってデータを操作する仕様が異なっているのだけど、それらの差異を可能な限り吸収しようという仕組みがJDBCである。でもって、Javaコードから呼び出されて、実際にDBサーバを操作しているのがJDBCドライバである。これは各DBの開発元が用意している。Oracleであれば、ojdbc7.jarとかojdbc6.jarとかいうJarファイルがそれだ。
 各DB用のなんたら.jarは、それぞれのDBのサイトからダウンロードできるのだが、Oracleの場合、OTNとかいうのに登録する必要がある。「何故わたしの本名や住所や電話番号を知りたいのだろうか? ひょっとしてストーカーかっ!」と思うところではあるが、登録すればOracle Databaseとその関連ツール類も使えたりするので、Oracleを用いたプロジェクトに携わる機会がある人は登録しておくと良いと思う。もちろん、Oracle Databaseに関しては開発者ライセンスという事になるので、商用利用というか実用的なものには使用出来ない。
 それを考えるとPostgreSQL辺りを例に挙げた方が良いのだろうけど、今やっている仕事がOracleを使っているので、Oracleの方が話が早かったりする。また、Oracle固有のネタも多少はやりたいといった理由もあって、今はOracleを使って話を進める。

 JBDCを使った処理は下記の通りである。

  1. DBサーバに接続する
  2. DBサーバにSQLを投入する
  3. DBサーバに投入したSQLを実行させる
  4. DBサーバから実行結果を順次取り出す(SELECT文の場合)
  5. リソースをクローズし、DBサーバの接続を切断する

 基本的にはこれだけである。何やらDBを使った処理を書く事が大変な事のように語られたりするが、DBに複雑な事をさせようとしなければ、それほど大変な事ではない。今回はJDBCを使ったサンプルコードを書いてみた。

 その前に処理対象のテーブルだが、今回は下記のようなテーブルを用意した。

 上記テーブルのDDLは下記になる。データは適当に入れて欲しい。ちなみに上記の例は、勿論架空の名前である。なんとなく聞いた事があるような名前だが、確かに架空の名前である。

  CREATE TABLE "MIYACHO"."M_M_MEMBER" 
   (
	"MEMBER_SID" NUMBER(18,0) NOT NULL ENABLE, 
	"CODE" VARCHAR2(10 BYTE) NOT NULL ENABLE, 
	"NAME" VARCHAR2(80 BYTE) NOT NULL ENABLE, 
	"NAME_KANA" VARCHAR2(80 BYTE), 
	"BIRTH" DATE, 
	"HEIGHT" NUMBER(3,0), 
	"WEIGHT" NUMBER(3,0), 
	 CONSTRAINT "M_M_MEMBER_PK" PRIMARY KEY ("MEMBER_SID")
   )

 そして、Javaのソースコードは下記になる。今回は、MEMBER_SID, CODE, NAMEの3つのカラムをコンソールに出力するだけのシンプルなものにした。

package jp.miyacho.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Sample1 {

    public static void main(String[] args) {
        System.exit(new Sample1().exec());
    }

    public int exec() {
        int rtn = 0;
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            connection
                = DriverManager.getConnection(
                "jdbc:oracle:thin://@localhost:1521:miyacho",
                 "miyacho",
                 "miyacho");

            ps = connection.prepareStatement("SELECT MEMBER_SID, CODE, NAME FROM M_M_MEMBER");
            rs = ps.executeQuery();
            while (rs.next()) {
                System.out.print("MEMBER_SID=" + rs.getLong(1));
                System.out.print(", CODE=" + rs.getString(2));
                System.out.println(", NAME=" + rs.getString(3));
            }
        } catch (ClassNotFoundException e) {
            rtn = 2;
            e.printStackTrace();
        } catch (SQLException e) {
            rtn = 3;
            e.printStackTrace();
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    rtn = 3;
                    e.printStackTrace();
                }
            }

            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    rtn = 3;
                    e.printStackTrace();
                }
            }

            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    rtn = 3;
                    e.printStackTrace();
                }
            }
        }

        return rtn;
    }
}
Sample1.java

 finallyブロックが長いのでサンプルというには長く感じるかもしれないが、重要なのは赤字の行である。そう考えると大した話ではない。下記に詳細を書く。

0.おまじない

 まず、Class.forName()というのは、おまじないである。と言うと怒られそうだが、本当にそうなのだから仕方が無い。やっている事は、"oracle.jdbc.driver.OracleDriver"というクラスをロードしているだけだからだ。クラスをロードする事により、oracle.jdbc.driver.OracleDriverクラスのstatic領域の初期化を行っているのである。初期化が失敗した場合は、例外が発生するので後続の処理を無視して終了することになる。ちなみにこの行は無くても動いたりする。最初に書く時は「OracleのJDBCドライバのクラス名は何だっけ?」となるが、そういう時は遠慮しないでググれば良い話だ。

1.DBサーバに接続する

 次にDriverManager.getConnection()だが、このメソッドでDBに接続する。引数は、DBサーバがネットワーク上の何処にあるのか示すURL、ユーザ名、パスワードの3つである。URLについては、"jdbc:oracle:thin"の部分がドライバ名を表している。この場合、Oracle thinドライバを使いますということになる。"@localhost:1521"がホスト名とポート番号になる。今回は自分のマシン内にDBを構築をしたのでlocalhostというホスト名を使っている。ネットワーク上の他のマシンに構築した場合は、そのアドレスを指定すれば良い。ちなみに最近のOracleJDBCドライバの場合、@の前に"//”を付けないとエラーになる場合があるので要注意だ。最後の"miyacho"の部分は、SIDとかサービス名になる。今回は、このサンプル用に"miyacho"というSIDを作ったので"miyacho"となっている。URLの形式についてはググれば良くて、個々のアドレスとかSIDの部分はDBを構築した人が良く知っているだろうから、その人に聞けば良い。

2.DBサーバにSQLを投入する

 接続が完了したら、DriverManager.getConnection()の戻り値としてjava.sql.Connectionクラスのオブジェクトが返ってくる。そのConnectionに対してprepareStatement()メソッドを実行することでSQLを投入する。投入するとjava.sql.PreparedStatementクラスのオブジェクトが返ってくる。Connectionに対してcreateStatement()メソッドを実行して、java.sql.Statementクラスのオブジェクトを返してもらうというやり方もあるが、Statementクラスはバインド変数を使う事による高速化やSQLインジェクション対策が使えないので、PreparedStatementクラスを使う方が良いだろう。

3.DBサーバに投入したSQLを実行させる

 PreparedStatementクラスのオブジェクトに対してexecute()、executeQuery()、executeUpdate()のいずれかのメソッドを実行すると投入したSQLが実行される。executeQuery()はSELECT文を実行する場合に用い、それ以外の場合はexecuteUpdate()文を使用する。execute()はいずれのSQLにも使用可能であるが、結果を取り出すにはメソッドの戻り値ではなく、別のメソッドを使用する必要がある。
 今回はSELECT文の実行なので、executeQuery()を使用している。SQLを実行する前にはPreparedStatementクラスのオブジェクトにsetInt()やsetString()といったsetterメソッドを使ってバインド変数を設定するが、今回のSQLはバイント変数を使わないのでsetterについて記述していない。

4.DBサーバから実行結果を順次取り出す

 PreparedStatementクラスのオブジェクトに対してexecuteQuery()を実行するとSQLの実行結果として、ResultSetクラスのオブジェクトが返ってくる。このResultSetクラスのオブジェクトに対して、getInt()やgetString()といったgetterメソッドを実行すると結果が取得できる。SELECT文の取得対象カラムの形式がnumber型のような数値型であれば、getInt()やgetLong()を使用する。varchar型のような文字列型であれば、getString()を使う。その他、Date型など適した型のgetterがあるはずなので、それを使えば良い。
 ただし、その前にnext()メソッドを実行して対象のレコードをセットしなくてはならない。next()メソッドは実行する度に次のレコードがセットされる。セットするレコードが存在する場合はnext()メソッドの戻り値はtrueとなり、次のレコードが存在しない場合はfalseとなる。while文の条件式にnext()メソッドを設定することにより、全ての結果について処理をするというコードになる。Javaで良く使用されるIjava.util.Iteratorの場合、hasNext()が次の値が存在するかを示すメソッドで、next()が値の取得と次の処理対象をセットするメソッドとなっているが、混同しないように注意が必要である。

5.リソースをクローズし、DBサーバの接続を切断する

 処理が終わったら各種リソースを確実にクローズし、DBサーバとの接続を切断する。今回のサンプルでのリソースは、Connection、PreparedStatement、ResultSetの3つになる。これらのクローズを忘れるとリソースリークが発生することになる。簡単な動作確認程度では、リソースリークが発生している事がわからなかったりするので注意が必要である。リソースのクローズに関しては、JDBCに限らずClosableなクラスを用いている場合は、確実に行われるように注意したいポイントである。
 各リソースに対してclose()メソッドを呼ぶ時は、処理中に例外が発生しても必ずリソースがクローズされるようにfinallyブロックで行う。finallyブロックはtryブロックと変数のスコープが別なので、各リソースの変数はtryブロックの外で宣言をする。一応オープンした順序と逆の順序でリソースをクローズしていくようにするが、例外が発生した場合、処理がどこまで行われるかわからないため、nullチェックを行ってからクローズしている。注意が必要なのは、close()メソッドでも例外が発生する可能性があることである。そのため各リソースのクローズ処理をさらにtryブロックで囲って、後続のクローズ処理がスキップされないようにする。
 このように書くと長ったらしくなるので、Java7以降ではtry-with-resourceという記述方法が導入されている。今回のサンプルは基本的な書き方を優先するためにfinallyブロックでクローズする方で記述した。

 知ってる人には当たり前過ぎるくらい当たり前の話なのだが、真面目に書くと結構な量の文章になるものだ。というか、本当は更に図や例を交えてわかりやすく書きたかったのだが、そこまでの気力が湧かなかった。このページはいずれ書き足す事にして、今回は長くなり過ぎたのでここまでにしておく。

Pervious < じゅんび2   Next >  JDBCサンプル(2)

お問い合わせは右のボタンをクリック→

Copyright(C) 2016 miyacho.com