JDBC

1.JDBCドライバ

JDBCは、Javaからリレーショナルデータベースに接続するためのしくみです。JDBCを使えば、さまざまなデータベースに、同じプログラミングで接続することができます。
JDBCを使って、ある特定のデータベースに接続するためには、そのデータベースのためのドライバが必要になります。このドライバをJDBCドライバといいます。
JDBCドライバは、データベースへの接続形態の違いによって4つのタイプに分けられます。

COMPUTERWORLD キーワード解説
http://www.computerworld.jp/resource/keyword/back/20000630.html

@IT 新人SEのためのJava講座 Javaデータアクセスの基礎
http://www.atmarkit.co.jp/fjava/rensai/jdbc01/jdbc01.html

1.1.タイプ1:ODBC-JDBCブリッジドライバ

タイプ1のドライバは、ODBC(Open DataBase Connectivity)ドライバを利用するドライバです。
ODBCは、主にWindowsで利用されているデータベース接続技術です。結構古くからあるため、Windows用のODBCドライバが用意されていないデータベースは存在しないと言えるほど普及しています。
そのため、Windows上でタイプ1のドライバを使えば、ほとんどのデータベースに接続することができると言えます。
また、JDKに標準でついてくるので、もっとも手軽なドライバです。

ただし、ODBCに変換する必要があることで速度などが低下することや、Javaプログラムとは別にODBCドライバが必要になるため、「Javaが動きさえすればどこでも動く」とは言えなくなります。
fortedb3/dbjdbc1.png

1.2.タイプ2:ネイティブブリッジドライバ

タイプ2のドライバは、Java用ではないドライバをJavaから呼び出すドライバです。
こちらも、Javaプログラムとは別にJava用ではないドライバが必要になるため、「Javaが動きさえすればどこでも動く」とは言えなくなります。
fortedb3/dbjdbc2.png

1.3.タイプ3:ネットプロトコルドライバ

タイプ3は、Javaプログラムからは、データベース共通のネットプロトコルを利用して、中間サーバーでデータベース固有のプロトコルに変換する方式です。
クライアント側ではJava用ではないドライバを使うことはないので、Javaが動きさえすればどこでも動くといえます。
ただし、中間サーバーが必要になるのでシステムの構成が複雑になります
fortedb3/dbjdbc3.png

1.4.タイプ4:ネイティブプロトコルドライバ

タイプ4は、直接データベース固有のプロトコルで接続するドライバです。
システム構成がもっともシンプルになります。
ただし、このタイプのJDBCドライバはサイズが大きくなる傾向があります。
そのせいもあって、すべてのデータベースに用意されているとは限りません。
fortedb3/dbjdbc4.png

2.抽出SQLの実行

それでは、JDBCを使ったプログラムの確認をします。
次のソースコードはJdbcSample1のものです。

ソースの先頭

package database;
・・・・(1)
import java.sql.*;

btnPrintのActionPerformedイベント

    private void btnPrintActionPerformed(java.awt.event.ActionEvent evt) {
        try{
・・・・(2)
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
        }
・・・・(3)
        catch(ClassNotFoundException e){
            e.printStackTrace();
            return;
        }
        try{
・・・・(4)
            Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
・・・・(5)
            Statement stmt = con.createStatement();
・・・・(6)
            ResultSet rs = stmt.executeQuery("select * from shohin_list");
            taMessage.setText("");
・・・・(7)
            while(rs.next()){
                taMessage.append(
・・・・(8)
                    rs.getString("shohin_cd") + ":" +
                    rs.getString("shohin_nm") + ":" +
                    rs.getString("maker_nm") + ":" +
                    rs.getString("bunrui_nm") + ":" +
                    rs.getString("price") + "\n");
            }
・・・・(9)
            rs.close();
            stmt.close();
            con.close();
        }
・・・・(10)
        catch(SQLException e){
            e.printStackTrace();
        }
    }
  • import
    データベースを扱うための基本的なクラスはすべて「java.sql」パッケージに含まれるので、「import java.sql.*」としてパッケージ名を省略できるようにします(1)。

  • JDBCドライバの読込
    まず、JDBCドライバを読みこむ必要があります(2)。
    Classクラスの静的メソッド「forName」を使います。

  • データベースへの接続
    ドライバーマネージャーから、データベースへの接続を得ます(3)。
    DriverManagerクラスの静的メソッド「getConnection」へ接続文字列を渡して呼び出すと、適当なJDBCドライバを選んでデータベースへ接続してくれます。
    接続文字列はJDBCドライバによって違いますが、ここではJDBC-ODBCブリッジドライバを使うので「jdbc:odbc:データソース」という形で指定します。
    このように、間接的にデータベースに接続しているのは、データベースが変わったときのソースコードの変更を最小限にできるようにするためです。

  • Statementの作成
    接続オブジェクトから、createStatementメソッドで、SQLの実行を管理するためのStatementオブジェクトを作成します(4)。

  • SELECT SQLの実行
    SELECT SQLを実行するときには、StatementオブジェクトのexecuteQueryメソッドを使います(5)。
    このとき、実行結果はResultSetオブジェクトとして返されます。

    そのときResultSetオブジェクトは、「先頭のレコードの直前」を参照しています。
    nextメソッドは、次のレコードを参照するメソッドですが、初めて実行したときには先頭のレコードを参照するようになります。
    抽出されたレコードがないときや、すでに最後のレコードを参照しているときには、戻り値としてfalseを返します。
    レコードを参照できたときにはtrueを返します。
    そのため、whileの条件にnextメソッドを指定することですべてのレコードの処理ができるようになります(6)。

    現在参照しているレコードからフィールドの値を取得するにはResultSetのgetXXXメソッドを使います(7)。getXXXメソッドとしては、getStringメソッドやgetIntメソッド、getTimeメソッドなどがありますが、フィールドの型に応じて使い分けます。引数としてフィールド名かフィールド番号を与えます。確実な動作のためにはフィールド名を指定した方がよいと思います。


    データベースの操作で使うConnectionやStatement、ResultSetなどの型は、ほとんどがクラスではなくてインターフェイスです。ただ、JDBCドライバを開発する立場にならない限りはクラスと同様に扱って問題ありません。

    java.sql.Connection
    データベースへの接続を管理する
      Statement createStatement() SQLをデータベースに送るためのStatementを生成する
      PreparedStatement prepareStatement(String sql) パラメータ付SQLを送るためのPreparedStatementを生成する
      void setAutoCommit(boolean autoCommit) 自動でコミットするかどうか設定する
      void commit() トランザクションで行われた変更を有効にする
      void rollback() トランザクションで行われた変更を無効にする
      void close() 接続を解除する

    java.sql.Statement
    SQLを実行するためのオブジェクト
      ResultSet executeQuery(String sql) 結果を返すSQLを実行する
      void executeUpdate(String sql) データを変更するSQLを実行する
      int getUpdateCount() SQLが何件のデータに影響を与えたか

    java.sql.ResultSet
    データベースから返された結果
      boolean next() カーソルを次の行に移動
      String getString(String columnName) フィールドの値を文字列で得る
      int getInt(String columnName) フィールドの値を整数で得る
  • 3.テーブルの一覧

    テーブルの一覧など、そのデータベースの属性を扱うときにはDatabaseMetaDataを使います。
    「database」パッケージに「GUIフォーム|JFrame」を作成します。名前は「JdbcSample4」とします。
    fortedb3/jdbc04.png fortedb3/jdbc05.png
    コンポーネント名 クラス 説明
    btnTable JButton テーブル表示ボタン
    taMessage JTextArea 結果表示用

    ソースの先頭

    package database;
    

    import java.sql.*;


    btnPrintのActionPerformedイベント

        private void btnPrintActionPerformed(java.awt.event.ActionEvent evt) {
    

    try{ Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTables(null,null,"%",null); while(rs.next()){ taMessage.append(rs.getString("TABLE_TYPE") + ":"); taMessage.append(rs.getString("TABLE_NAME") + "\n"); } rs.close(); con.close(); } catch(ClassNotFoundException e){ e.printStackTrace(); } catch(SQLException e){ e.printStackTrace(); }

    }
    実行してボタンを押すと、このように、テーブルの一覧が表示されます。
    fortedb3/jdbc07.png

    getTablesメソッドはResultSetを返すので、SQLの結果表示と同様のコーディングでテーブルの一覧を表示します。

    ただ、ここでは、Accessが内部的に使っているようなテーブルも表示されてしまっています。
    そこでgetTablesの4番目の引数に取得するテーブルの種類を指定することで、必要なテーブルだけを表示することができます。

    btnPrintのActionPerformedイベント

        private void btnPrintActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
    
                DatabaseMetaData dbmd = con.getMetaData();
    
    

    ResultSet rs = dbmd.getTables(null,null,"%",new String[]{"TABLE","VIEW"});

    while(rs.next()){ taMessage.append(rs.getString("TABLE_TYPE") + ":"); taMessage.append(rs.getString("TABLE_NAME") + "\n"); } rs.close(); con.close(); } catch(ClassNotFoundException e){ e.printStackTrace(); } catch(SQLException e){ e.printStackTrace(); } }

    これで、テーブルとクエリーだけが表示されるようになりました。
    Accessのクエリーはビューとして扱われます。
    fortedb3/jdbc06.png

    4.フィールドの一覧

    フィールドの一覧を得るためには、ResultSetMetaDataを使います。

    JdbcSample4を変更します。
    fortedb3/jdbc01.png fortedb3/jdbc02.png
    コンポーネント名 クラス 説明
    btnTable JButton テーブル表示ボタン
    btnField JTextField テーブル名入力
    btnField JButton フィールド表示ボタン
    taMessage JTextArea 結果表示用

    btnFieldのActionPerformed

        private void btnFieldActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
    
    

    Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select * from " + txtField.getText()); ResultSetMetaData meta = rs.getMetaData(); int columncount = meta.getColumnCount(); taMessage.append("-- " + txtField.getText() + "--\n"); for (int i = 0; i < columncount; ++i){ taMessage.append(meta.getColumnName(i + 1) + "\n"); } rs.close(); stmt.close();

    con.close(); } catch(ClassNotFoundException e){ e.printStackTrace(); } catch(SQLException e){ e.printStackTrace(); } }
    実行して、テーブル名を指定してから「フィールド一覧」ボタンを押すと、そのテーブルのフィールドが表示されます。
    fortedb3/jdbc03.png

    JdbcSample2などでは、このResultSetMetaDataを利用して、SQLの結果に応じた列名を取得していました。

    btnExecのActionPerformedイベント

       private void btnExecActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                Statement stmt = con.createStatement();
    
                //SQLの実行
                ResultSet rs = stmt.executeQuery(taSql.getText());
    
    

    //フィールドを取得 ResultSetMetaData meta = rs.getMetaData(); int columncount = meta.getColumnCount(); String[] fieldname = new String[columncount]; for (int i = 0; i < columncount; ++i){ fieldname[i] = meta.getColumnName(i + 1); }

    //データを取得 Vector v = new Vector(); while(rs.next()){ Object[] o = new Object[columncount]; for(int i = 0; i < columncount; ++i){ o[i] = rs.getObject(i + 1); } v.add(o); } Object[][] oaa = (Object[][])v.toArray(new Object[v.size()][]); //JTableに設定 tableResult.setModel(new DefaultTableModel(oaa,fieldname)); con.close(); } catch(ClassNotFoundException e){ e.printStackTrace(); } catch(SQLException e){ e.printStackTrace(); } }

    5.変更SQLの実行

    SELECT以外の、結果セットを返さないSQLを実行するには、executeUpdateメソッドを使う必要があります。

    JdbcSample3(一部)

        private void btnOrderActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                Statement stmt = con.createStatement();
    
    

    stmt.executeUpdate(taSql.getText());

    //表示用SQLの実行 String sql = ""; sql = "select shohin_cd,shohin_nm,maker_nm,bunrui_nm,price,juni"; sql += " from (shohin left join maker on shohin.maker_cd=maker.maker_cd)"; sql += " left join bunrui on shohin.bunrui_cd=bunrui.bunrui_cd"; sql += " order by shohin_cd"; ResultSet rs = stmt.executeQuery(sql);

    5.1.順位つけ

    Statementでは、ひとつのResultSetを保持している間は別のSQLを実行することができません。
    ResultSetを開いているときに他のSQLを実行すると、そのResultSetは自動的に閉じられてしまいます。
    そのため、あるSQLの結果からべつのSQLを作成して実行したいときなどでは、Statementを複数用意する必要があります。
    ここでは、そのような処理の典型として、順位付けをしています。

    「JdbcSample3」をコピーして「JdbcSample5」とします。mainメソッドの変更を忘れないようにしてください。
    変更ミスを避けるために、「ソースエディタ」のウィンドウを閉じてからコピーを行った方がいいと思います。
    fortedb3/jdbc08.png fortedb3/jdbc09.png
    コンポーネント名 クラス 説明
    taSql JTextArea 未使用
    btnOrder JButton 順位付けボタン
    tableResult JTable 結果表示用テーブル

    btnOrderのActionPerformed前半

        private void btnOrderActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                Statement stmt = con.createStatement();
    
    

    String sql; Statement stmt2 = con.createStatement(); //順位をつける sql = "select * from shohin order by price"; ResultSet rs = stmt.executeQuery(sql); for (int i = 0; rs.next(); ++i){ sql = "update shohin set juni=" + (i + 1); sql += " where shohin_cd=" + rs.getString("shohin_cd"); stmt2.executeUpdate(sql); } rs.close(); stmt2.close();

    //表示用SQLの実行

    sql = "";

    sql = "select shohin_cd,shohin_nm,maker_nm,bunrui_nm,price,juni"; sql += " from (shohin left join maker on shohin.maker_cd=maker.maker_cd)"; sql += " left join bunrui on shohin.bunrui_cd=bunrui.bunrui_cd"; sql += " order by shohin_cd";

    rs = stmt.executeQuery(sql);


    実行してボタンを押すとjuniフィールドにprice順の順位がつけられます。
    fortedb3/jdbc10.png

    ここでは、priceで並べ替えるSQLから得られたResultSetを元に、順位をつけるupdate SQLを実行する必要があったので、2つのStatementが必要になったわけです。
    順位つけのような、他のレコードとの関係から値を求めるような処理は、SQLが不得意とする処理なので、単独のSQLで実行することはできません。

    6.プリペアドステートメント

    ここで、順位をつけるupdate SQLは、プログラム中で生成しています。
    このように、実行時に一部が変わるような動的なSQLを実行するときには、プリペアドステートメントを使うほうが確実で、プログラムの見とおしもよくなります。

    java.sql.PreparedStatement
    一部をパラメータ化したSQLを扱う
      ResultSet executeQuery() 結果を返すSQLを実行する
      void executeUpdate() データを変更するSQLを実行する
      void setInt(int parameterIndex,int value) パラメータに整数値を設定する
      void setString(int parameterIndex,String value) パラメータに文字列を設定する

    btnOrderのActionPerformed前半

        private void btnOrderActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                Statement stmt = con.createStatement();
    
                String sql;
    
    

    sql = "update shohin set juni=? where shohin_cd=?"; PreparedStatement pstmt = con.prepareStatement(sql);

    //順位をつける

    sql = "select * from shohin order by price desc";

    ResultSet rs = stmt.executeQuery(sql); for (int i = 0; rs.next(); ++i){

    pstmt.setInt(1, i + 1); pstmt.setString(2,rs.getString("shohin_cd")); pstmt.executeUpdate();

    } rs.close();

    pstmt.close();

    //表示用SQLの実行 sql = ""; sql = "select shohin_cd,shohin_nm,maker_nm,bunrui_nm,price,juni"; sql += " from (shohin left join maker on shohin.maker_cd=maker.maker_cd)"; sql += " left join bunrui on shohin.bunrui_cd=bunrui.bunrui_cd"; sql += " order by shohin_cd"; rs = stmt.executeQuery(sql);

    order by句でdescを付け加えているので、priceの高い順に順位がつけられます。
    fortedb3/jdbc12.png

    PreparedStatementでは、あとで置きかえる部分を「?」としておいて、prepareStatementを実行します。
    ここで、型は「準備されたステートメント」、メソッドは「ステートメントを準備する」となるので「d」がついたりついてなかったりします。
    実行時には、setXXXメソッドを使って「?」としておいたところに値を設定します。
    setXXXメソッドには、setIntメソッドやsetStringメソッドなどがあるので、型に応じて使い分けます。
    setXXXメソッドの第一引数は、何番目の「?」に値を設定するかを指定する番号です。最初の「?」が1になります。2番目の引数が設定する値です。

    7.アップデータ―メソッド

    順位つけの例のように、ResultSetで参照中のレコードを変更するときなどには、アップデータ―メソッドを使うことができます。

    btnOrderのActionPerformed前半

        private void btnOrderActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                Statement stmt = con.createStatement();
    
                String sql;
    
                //順位をつける
    

    Statement stmt2 = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE); sql = "select * from shohin order by price desc"; ResultSet rs = stmt2.executeQuery(sql);

    for (int i = 0; rs.next(); ++i){

    rs.updateInt("juni",i + 1); rs.updateRow();

    } rs.close();

    stmt2.close();

    //表示用SQLの実行 sql = ""; sql = "select shohin_cd,shohin_nm,maker_nm,bunrui_nm,price,juni"; sql += " from (shohin left join maker on shohin.maker_cd=maker.maker_cd)"; sql += " left join bunrui on shohin.bunrui_cd=bunrui.bunrui_cd"; sql += " order by shohin_cd"; rs = stmt.executeQuery(sql);

    ここでは、商品名の順番に順位がつけられます。
    fortedb3/jdbc11.png

    アップデータ―メソッドには、列の値を変更するupdateXXXメソッドと、その変更を反映させるためのupdateRowメソッドなどがあります。
    ここでは、updateIntメソッドを使って、参照中のレコードのjuniフィールドの値を変更しています。それから、updateRowメソッドで変更を反映しています。

    レコードの挿入もできますが、ここでは説明しません。APIドキュメントに例が書いてあります。

    8.PreparedStatement

    fortedb3/jdbc16.png
    コンポーネント名 クラス 説明
    btnSearch JButton 検索ボタン
    taOutput JTextArea 結果表示用
    package database;
    
    import java.sql.*;
    

    btnSearchのActionPerformed

        private void btnSearchActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            }
            catch(ClassNotFoundException e){
                taOutput.append("forNameの中身まちがってないですか?\n");
                return;
            }
            try{
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
                String sql = "select * from maker where maker_cd=" + txtCode.getText();
                Statement stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery(sql);
                if(rs.next()){
                    //見つかった
                    taOutput.append("みつかりました\n");
                    taOutput.append("コード:" + rs.getString("maker_cd") + "\n");
                    taOutput.append("名前:" + rs.getString("maker_nm") + "\n");
                    taOutput.append("\n");
                }
                else{
                    //見つからない
                    taOutput.append(txtCode.getText() + "はみつかりませんでした\n\n");
                }
                rs.close();
                stmt.close();
                con.close();
            }
            catch(SQLException e){
                taOutput.append("データベースエラー:" + e.toString() + "\n");
                e.printStackTrace();
            }
        }
    
    fortedb3/jdbc13.png
    fortedb3/jdbc14.png

    btnSearchのActionPerformed

        private void btnSearchActionPerformed(java.awt.event.ActionEvent evt) {
            try{
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            }
            catch(ClassNotFoundException e){
                taOutput.append("forNameの中身まちがってないですか?\n");
                return;
            }
            try{
                Connection con = DriverManager.getConnection("jdbc:odbc:jdbcsample");
    

    String sql = "select * from maker where maker_cd=?"; PreparedStatement pstmt = con.prepareStatement(sql); pstmt.setString(1,txtCode.getText()); ResultSet rs = pstmt.executeQuery();

    if(rs.next()){ //見つかった taOutput.append("みつかりました\n"); taOutput.append("コード:" + rs.getString("maker_cd") + "\n"); taOutput.append("名前:" + rs.getString("maker_nm") + "\n"); taOutput.append("\n"); } else{ //見つからない taOutput.append(txtCode.getText() + "はみつかりませんでした\n\n"); } rs.close();

    pstmt.close();

    con.close(); } catch(SQLException e){ taOutput.append("データベースエラー:" + e.toString() + "\n"); e.printStackTrace(); } }
    fortedb3/jdbc15.png

    Copyright (c) 2001-2003 Naoki Kishida All Rights Reserved.
    http://www.fk.urban.ne.jp/home/kishida/