データベースの利用

概要

PostgreSQLの環境設定 - 詳細

以降のチュートリアルで必要となるデータベース及びテーブルを、PostgreSQLで作成します。

JDBC - 詳細

JDBCを使ったデータベースアクセスの方法を理解します。

INSERT文の実行 - 詳細

INSERT文を実行することにより、JDBCを使ったデータベースアクセスの基礎を理解します。

UPDATE文の実行 - 詳細

JDBCを使ったUPDATE文の実行方法を理解します。

DELETE文の実行 - 詳細

JDBCを使ったDELETE文の実行方法を理解します。

SELECT文の実行 - 詳細

JDBCを使ったSELECT文の実行方法を理解します。

トランザクション処理 - 詳細

JDBCを使って、トランザクション処理を伴うデータベースアクセスの実行方法を理解します。

特殊文字のエスケープ - 詳細

特殊文字をエスケープする必要性と、その方法を理解します。

PreparedStatement?の使用 - 詳細

PreparedStatement?クラスを使ったSQLの実行方法を理解します。

総合課題 - 詳細

今までに出てきた知識を活用して、以前に作成した掲示板を改造します。

詳細

PostgreSQLの環境設定

PostgreSQLでのデータベース、テーブル作成手順

  1. pgAdminⅢを起動し、サーバーに接続します。
    pgAdminⅢサーバーに接続
     
  2. ログインロールを追加します。
    1. 下図の様に操作し、新しいログインロール登録ウインドウを表示します。
      ログインロール登録ウインドウ表示
       
    2. ロール名、パスワード、権限等を入力しOKボタンを押下します。
      (図ではロール名、パスワード共に「user01」で作成しています。)
      ログインロール入力
       
      以上でログインロール登録完了です。
       
  3. データベースを作成します。
    1. 下図の様に操作し、新しいデータベース作成ウインドウを表示します。
      新しいデータベース作成ウインドウ表示
       
    2. 下図の様に入力してOKボタンを押下し「kensyu」という名前のデータベースを作成します。
      (オーナーは先ほど登録したロールを指定しています。)
      ログインロール入力
       
      以上でデータベース作成完了です。
       
  4. テーブルを作成します。
    1. 下図の様に操作し、新しいテーブル作成ウインドウを表示します。
      新しいテーブル作成ウインドウ表示
       
    2. 名前欄に「sampletable」と入力し、オーナー、テーブル空間は下図のようにします。
      テーブルプロパティ入力
       
    3. テーブルに列(カラム)を追加していきます。
      新しいテーブル作成ウインドウの列タブを選択して追加ボタンを押下し、新しい列作成ウインドウを表示します。
      列の追加
       
      1. 名前欄に「pk」と入力し、データ型は「text」を選択してOKボタンを押下します。
        新しい列「pk」
         
      2. もう一度追加ボタンを押下し、新しい列作成ウインドウを表示します。
        名前欄に「textcolumn」と入力し、データ型は「text」を選択してOKボタンを押下します。
        新しい列「textcolumn」
         
      3. もう一度追加ボタンを押下し、新しい列作成ウインドウを表示します。
        名前欄に「numbercolumn」と入力し、データ型は「numeric」を選択してOKボタンを押下します。
        新しい列「numbercolumn」
         
      4. もう一度追加ボタンを押下し、新しい列作成ウインドウを表示します。
        名前欄に「datecolumn」と入力し、データ型は「date」を選択します。
        さらに初期の値欄に「now()」と入力し、OKボタンを押下します。
        新しい列「datecolumn」
         
         
        新しいテーブル作成ウインドウの列タブの項目が下図の様になります。
        列項目
         
    4. 主キーを設定します。
      1. 新しいテーブル作成ウインドウの制約タブを選択して制約設定画面を表示します。
        制約名リストから「主キー」を選択して追加ボタンを押下し、新しい主キー設定ウインドウを表示します。
        新しい主キー設定ウインドウを表示
         
      2. 列タブを選択し、リストから「pk」を選択し追加ボタンを押下して主キーを設定します。
        します。
        主キー設定
         
      3. OKボタンを押下すると、新しいテーブル作成ウインドウの制約タブの項目が下図の様になります。
        制約項目
         

新しいテーブル作成ウインドウのOKボタンを押下します。
pgAdminⅢのメイン画面が下図のようになっていれば作成完了です。

完了
 

課題
以上の作業を実際に行ってみましょう。

JDBC

JDBC
Javaでデータベースアクセスを行うときに使用する標準APIのことを、「JDBC」と呼びます。
JDBCに関連するクラスは、「java.sql」パッケージに含まれています。

JDBCを使ったデータベースアクセスの基本的な処理の流れは、次のようになります。

  1. コネクションの取得
  2. SQLの実行
  3. コミット または ロールバック
  4. リソースの解放

使用するDBによって、コネクション取得時にロードするドライバやURLの指定方法が異なります。
業務でよく使用される主要なDBのロードするドライバやURL指定方法は下表の通りです。

DBドライバURL
PostgreSQLorg.postgresql.Driverjdbc:postgresql://ホスト名:ポート番号/DB名
MySQLorg.gjt.mm.mysql.Driverjdbc:MySQL://ホスト名:ポート番号/DB名
Oracleoracle.jdbc.driver.OracleDriver?jdbc:oracle:oci:@接続するデータベースのSID
jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID

INSERT文の実行

INSERT文の実行
以下に、INSERT文を実行するクラス「DBInsert」のソースを示します。
このクラスはサーブレットではなく、mainメソッドから始まる通常のJavaの実行クラスです。
サンプルソース:DBInsert.java

//==========================================================
//import

//J2SE ・・・1
import java.sql.*;

/**
 * INSERT文を実行するクラス。<br>
 */
public class DBInsert 
{
//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception 
  {
    //コネクション
    Connection con = null;

    //ステートメント
    Statement stm = null;

    try
    {
      //==========================================================
      //コネクションの取得
            
    //***** JDBCドライバのロード ******* ・・・2
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");
            
      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");
            
      //Oracleの場合
      //Class.forName("oracle.jdbc.driver.OracleDriver");
    //**********************************
            
    //***** URLを指定してコネクションを取得 ******* ・・・3
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする ・・・4
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

 
      //==========================================================
      //SQLの実行

      //コネクションからステートメントを取得 ・・・5
      stm = con.createStatement();

      //INSERT文を実行して更新行数を取得 ・・・6
      int updateLine = stm.executeUpdate("INSERT INTO sampletable(pk, textcolumn, numbercolumn) VALUES('主キーの値', 'テキストの値', 1)");

      //更新行数を出力
      System.out.println("更新行数=" + updateLine);

 
      //==========================================================
      //コミット ・・・7
      con.commit();
    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック ・・・8

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

          //catchした例外を投げなおす
          throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放 ・・・9

      //ステートメントがnullでない場合
      if(stm != null)
      {
        //ステートメントを閉じる
        stm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

ポイントを整理すると、次のようになります。

  1. 「java.sql」パッケージをimportする。
  2. JDBCドライバをロードする。
    • JDBCドライバ(実際にデータベースアクセスを行うクラス群)は、このようにしてメモリ上にロードしないと使用できません。
    • JDBCドライバは各ベンダから提供されているので、環境に応じてこの記述は変更する必要があります。
    • ここで指定している「org.postgresql.Driver」というJDBCドライバは、PostgreSQL用のJDBCドライバで、PostgresSQLデータベースサーバとの接続を行います。
      ※ あらかじめ、使用しているJDKのバージョンに適応したjarファイルを取得し、パスを通しておく必要があります。
    • java.lang.Class クラスの forName() メソッドは、 java.lang.ClassNotFoundException? を投げるので注意が必要です。
  3. コネクションを取得する。
    • ここで取得するコネクションクラス(java.sql.Connection)は、JDBCを使ったデータベースアクセスの核となるクラスです。
    • ここで指定するURLの形式はデータベースによって異なります。
    • ユーザ名とパスワードが必要な場合は、第2引数, 第3引数に指定します。
  4. 自動コミットをOFFにする。
    • この処理を行わないと、SQLを実行する度に勝手にコミットされてしまいますので、必ずOFFにしておいて下さい。
  5. ステートメントを取得する。
    • ここで取得するステートメントクラス(java.sql.Statement)が、実際にSQLを実行するクラスです。
  6. ステートメントの executeUpdate() メソッドを使ってSQLを実行する。
    • このメソッドは与えられた更新系のSQL(INSERT, UPDATE, DELETE)をそのまま実行して、更新行数を返します。
    • 参照系のSQL(SELECT)を実行するときは、executeQuery() メソッドを使用します。
    • 詳細は、SELECT文の実行を参照して下さい。
      ※ 補足:PostgreSQLではテーブル名やカラム名に大文字を含む場合は下記の様に名前を""で囲む必要があるので注意してください。
      executeUpdate("INSERT INTO \"SampleTable\"(\"PK\", \"TextColumn\", \"NumberColumn\") VALUES('主キーの値', 'テキストの値', 1)")
  7. コミットする。
    • この処理を忘れると、結果がデータベースに反映されません。
  8. 例外が発生したらロールバックする。
    • rollback() メソッドは java.sql.SQLException を投げるので、このように2重に try - catch で例外処理を行う必要があります。
  9. 終了処理で確実にリソースの解放を行う。
    • この処理を忘れると、取得したコネクションはガベージコレクションされるまで解放されません。
    • 順番は、「ステートメント - コネクション」というように、後で取得したものから解放します。

課題
上のソースを実行して、実際にデータベースにレコードが作成されることを確認してみましょう。
なお、このソースは一度目は成功しますが、2度目以降は主キーの重複エラーになり例外が発生しますので注意して下さい。

UPDATE文の実行

UPDATE文の実行
以下に、UPDATE文を実行するクラス「DBUpdate」のソースを示します。
基本的にはINSERT文の場合と同じで、実行するSQLの内容だけが変わっています。
サンプルソース:DBUpdate.java

//==========================================================
//import

//J2SE
import java.sql.*;

/**
 * UPDATE文を実行するクラス。<br>
 */
public class DBUpdate 
{
//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception 
  {
    //コネクション
    Connection con = null;

    //ステートメント
    Statement stm = null;

    try
    {
      //==========================================================
      //コネクションの取得
            
    //***** JDBCドライバのロード *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");
            
      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");
            
      //Oracleの場合
      //Class.forName("oracle.jdbc.driver.OracleDriver");
    //**********************************
            
    //***** URLを指定してコネクションを取得 *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

 
      //==========================================================
      //SQLの実行

      //コネクションからステートメントを取得
      stm = con.createStatement();

      //UPDATE文を実行して更新行数を取得
      int updateLine = stm.executeUpdate("UPDATE sampletable SET textcolumn='更新後のテキスト', numbercolumn=3 WHERE pk='主キーの値'");

      //更新行数を出力
      System.out.println("更新行数=" + updateLine);

 
      //==========================================================
      //コミット
      con.commit();
    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

          //catchした例外を投げなおす
          throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放

      //ステートメントがnullでない場合
      if(stm != null)
      {
        //ステートメントを閉じる
        stm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

注意
これはSQL全般の話でJDBCとは直接関係はないですが、UPDATE文にWHERE句を指定しないと全てのレコードが更新されて大変なことになってしまいます。

課題
上のソースを実行して、実際にデータベースが更新されることを確認してみましょう。

DELETE文の実行

DELETE文の実行
以下に、DELETE文を実行するクラス「DBDelete」のソースを示します。
基本的にはINSERT文やUPDATE文の場合と同じで、実行するSQLの内容だけが変わっています。
サンプルソース:DBDelete.java

//==========================================================
//import

//J2SE
import java.sql.*;

/**
 * DELETE文を実行するクラス。<br>
 */
public class DBDelete 
{
//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception 
  {
    //コネクション
    Connection con = null;

    //ステートメント
    Statement stm = null;

    try
    {
      //==========================================================
      //コネクションの取得
            
    //***** JDBCドライバのロード *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");
            
      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");
            
      //Oracleの場合
      //Class.forName("oracle.jdbc.driver.OracleDriver");
    //**********************************
            
    //***** URLを指定してコネクションを取得 *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

 
      //==========================================================
      //SQLの実行

      //コネクションからステートメントを取得
      stm = con.createStatement();

      //DELETE文を実行して更新行数を取得
      int updateLine = stm.executeUpdate("DELETE FROM sampletable WHERE pk='主キーの値'");

      //更新行数を出力
      System.out.println("更新行数=" + updateLine);

 
      //==========================================================
      //コミット
      con.commit();
    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

          //catchした例外を投げなおす
          throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放

      //ステートメントがnullでない場合
      if(stm != null)
      {
        //ステートメントを閉じる
        stm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

注意
これはSQL全般の話でJDBCとは直接関係はないですが、DELETE文にWHERE句を指定しないと全てのレコードが削除されて大変なことになってしまいます。

課題
上のソースを実行して、実際にデータベースが更新されることを確認してみましょう。

SELECT文の実行

SELECT文の実行
以下に、SELECT文を実行するクラス「DBSelect」のソースを示します。
基本的な流れはINSERT文などと同じですが、データの取得処理が含まれるので若干複雑になります。
サンプルソース:DBSelect.java

//==========================================================
//import

//J2SE
import java.sql.*;

/**
 * SELECT文を実行するクラス。<br>
 */
public class DBSelect 
{
//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception 
  {
    //コネクション
    Connection con = null;

    //ステートメント
    Statement stm = null;

    //ResultSet
    ResultSet rs = null;

    try
    {
      //==========================================================
      //コネクションの取得
            
    //***** JDBCドライバのロード *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");
            
      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");
            
      //Oracleの場合
      //Class.forName("oracle.jdbc.driver.OracleDriver");
    //**********************************
            
    //***** URLを指定してコネクションを取得 *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

 
      //==========================================================
      //SQLの実行

      //コネクションからステートメントを取得
      stm = con.createStatement();

      //SELECT文を実行して、結果の入ったResultSetのインスタンスを取得
      rs = stm.executeQuery("SELECT pk, textcolumn, numbercolumn, datecolumn FROM sampletable");

      //==========================================================
      //ResultSetの読み込み

      //全てのレコードを読み終えるまでカーソルを進める
      while(rs.next())
      {
        //カラム「pk」の値をStringで取得
        String pk = rs.getString("pk");

        //カラム「textcolumn」の値をStringで取得
        String textColumn = rs.getString("textcolumn");

        //カラム「numbercolumn」の値をintで取得
        int numberColumn = rs.getInt("numbercolumn");

        //カラム「datecolumn」の値をjava.sql.Dateで取得
        //(java.util.Dateでないので注意)
        Date dateColumn = rs.getDate("datecolumn");

 
        //このようにカラム名の代わりにインデックスを指定して取得することもできます
        //(インデックスは「0」からではなく、「1」から始まります)
        //String pk = rs.getString(1);

 
        //本来はここで取得した結果をListに入れたりしますが、ここでは単純に標準出力に出力します
        System.out.println("pk=" + pk);
        System.out.println("textColumn=" + textColumn);
        System.out.println("numberColumn=" + numberColumn);
        System.out.println("dateColumn=" + dateColumn);
      }

      //==========================================================
      //コミット
      con.commit();
    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

          //catchした例外を投げなおす
          throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放

      //ステートメントがnullでない場合
      if(stm != null)
      {
        //ステートメントを閉じる
        stm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

ResultSet?クラス
SELECT文の実行には、ステートメントクラスの executeQuery() メソッドを使用しますが、その戻り値は ResultSet?(java.sql.ResultSet?)という結果セットを表すクラスになります。
実装者は、このクラスを使って結果セットのカーソルを1つずつ進めていき、カーソル行の各カラムの値を取得します。

具体的な使用方法は、次のようになります。

  1. next() メソッドがtrueを返す間ループをまわす。
    • このメソッドはカーソルを1つ進めて、まだ読み込めるレコードがある場合にtrueを返します。
      これ以上読み込めるレコードがなくなった場合は、falseを返します。
    • next() メソッドを一度も呼んでいない段階では、カーソルはどのレコードも指していない状態なので、値を取得することができません。
      そのため、結果が1レコードしかないとわかっている場合も、必ず1度は next() メソッドを呼び出す必要があります。
  2. getXXX() メソッドで各カラムの値を取得する。
    • このメソッドの引数にはカラム名またはインデックスを指定します。
      インデックスは「0」からではなく、「1」から始まるので注意して下さい。
    • メソッド名のXXXの部分には「String」, 「int」などの具体的な型の名前が入ります。
      不適切な型を指定した場合には例外が発生します。
    • データベースの型とJavaのクラスとのマッピングは、データベースによって異なります。
  3. 取得した値をListなどに詰め替える。
    • ResultSet?は、ResultSet?自身・ステートメント・コネクションのいずれかのクラスの close() メソッドを呼び出すと閉じられます。
      ResultSet? が閉じられると、それ以降は値の取得ができなくなります。
      そのため、取得した値を画面に表示する場合などは、取得した値を別のクラスに詰め替えて渡す必要があります。

課題
上のソースを実行して、実際にデータベースの内容を取得していること確認してみましょう。

トランザクション処理

トランザクション処理とは
トランザクション処理とは、全ての処理が成功するまではデータベースに更新を反映してはいけない一連の処理のことです。
正常に処理が行われる場合は、トランザクション処理を行っていても行っていなくてもあまり違いはありませんが、途中で障害が発生した場合に大きな差が生じます。

例えば、銀行振り込みをする場合を考えて見ましょう。
A口座からB口座に10万円振り込むとき、まずA口座の金額を10万円減らしてからB口座の金額を10万円増やします。
もし、トランザクション処理を行っていないと、A口座の金額を10万円減らした段階で何らかの障害が発生した場合、その結果が反映されてしまい、B口座に振り込まれていないのにA口座の金額だけが減ってしまって大問題になってしまいます。
トランザクション処理を行っている場合は、A口座の金額も10万円減らす前の状態に戻るので大した問題は発生しません。

トランザクション処理を伴うデータベースアクセスの実行
以下に、トランザクション処理を伴うデータベースアクセスを行うクラス「DBTransaction」のソースを示します。
基本的な流れは今までなどと同じですが、コネクションを取得してからコミットまたはロールバックされるまでの間に複数のSQLが実行されています。
(自動コミットをOFFにしないと、トランザクション処理は実行できません。) サンプルソース:DBTransaction.java

//==========================================================
//import

//J2SE
import java.sql.*;

 
/**
 * トランザクション処理を伴うデータベースアクセスを実行するクラス。<br>
 */
public class DBTransaction
{
//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception
  {
    //コネクション
    Connection con = null;

    //ステートメント
    Statement stm = null;

    try
    {
      //==========================================================
      //コネクションの取得

    //***** JDBCドライバのロード *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");
            
      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");
            
      //Oracleの場合
      //Class.forName("oracle.jdbc.driver.OracleDriver");
    //**********************************
            
    //***** URLを指定してコネクションを取得 *******
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

 
      //==========================================================
      //SQLの実行

      //コネクションからステートメントを取得
      stm = con.createStatement();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //トランザクション処理を行うには、ここで複数のSQLを実行します

      //UPDATE文を実行して更新行数を取得
      int updateLine = stm.executeUpdate("UPDATE sampletable SET textcolumn='更新後のテキスト', numbercolumn=3 WHERE pk='主キーの値'");

      //更新行数を出力
      System.out.println("1つ目のSQLの更新行数=" + updateLine);

 
      //INSERT文を実行して更新行数を取得
      updateLine = stm.executeUpdate("INSERT INTO sampletable(pk, textcolumn, numbercolumn) VALUES('主キーの値2', 'テキストの値2', 2)");

      //更新行数を出力
      System.out.println("2つ目のSQLの更新行数=" + updateLine);

 
      //わざと例外が発生するSQLを実行
      //(ここで例外が発生するとトランザクションがロールバックされるので、上の2つのSQLの更新結果はデータベースに反映されません)
      updateLine = stm.executeUpdate("例外が発生するSQL文");

      //==========================================================
      //コミット
      con.commit();
    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

          //catchした例外を投げなおす
          throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放

      //ステートメントがnullでない場合
      if(stm != null)
      {
        //ステートメントを閉じる
        stm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

課題
上のソースは3つ目のSQLの実行時に必ず例外が発生します。 実際に実行して、最初の2つのSQLの更新処理がデータベースに反映されていないことを確認してみましょう。

特殊文字のエスケープ

特殊文字をエスケープする必要性
サーブレット - 特殊文字のエスケープと同様に、ユーザの入力値をそのままSQLとして実行してしまうと問題が生じます。
例えば、ユーザの入力値に「'」が含まれている場合、そのままSQLの値に設定して実行しようとすると、例外が発生します。
JDBCでは1度に1つのSQLしか実行できないので大丈夫ですが、「'; 何らかのSQL」のような文字列が入力されてそれをそのまま実行してしまうと、ユーザが入力したSQLがそのまま実行されてしまいます。
この問題は「SQL Injection 問題」と呼ばれ、以前に説明した「クロスサイトスクリプティング問題」と同様に重大なセキュリティホールとなります。
この問題を回避するためには、「'」を「''」(ダブルクォーテーションではなく、シングルクォーテーション2つ)というように2重にしてエスケープする必要があります。

補足
LIKE検索を行う際には、「'」に加えて「%」や「_」もエスケープする必要があります。
しかし、LIKE検索のエスケープの仕方はデータベースによってバラバラで、統一されていません。
さらに言えば、今回使用しているAccessではエスケープすることすらできません。
そういう理由で、ここではLIKE検索のエスケープは扱いませんが、エスケープの必要性があるということは覚えておいて下さい。

実装
上記の「'」をエスケープする専用のメソッドを作成し、パラメータを出力する場合は必ずそのメソッドを通すようにします。
さらに、そのメソッドを特殊文字のエスケープを行う専用のクラスに定義しておくと、どのクラスからも使用することができるので、より汎用性の高いプログラムを作成することができます。

課題
上の説明にある、特殊文字のエスケープを行う専用のメソッドを、サーブレット - 特殊文字のエスケープで作成したクラス「EscapeUtil?」に追加してみましょう。
追加するメソッドは、String escapeQuotation(String str) で、与えられた文字列中に含まれる「'」をエスケープしてから返します。
(このメソッドは、インスタンスを生成しなくても使えるよう、static にしておくと良いです。)

PreparedStatement?の使用

PreparedStatement?クラス
今まではSQLの実行にステートメントクラス(java.sql.Statement)を使用してきましたが、JDBCにはPreparedStatement?(java.sql.PreparedStatement?, プリコンパイル済みステートメント)というクラスもあります。
(本当はもう1つ、ストアドプロシージャを呼び出すための java.sql.CallableStatement? というクラスもありますが、ここでは扱いません。)

JDBCでSQLを実行するときには、実際には先にSQLのコンパイルが行われます。
ステートメントクラスの場合は、SQLの実行の直前にコンパイルが行われます。
PreparedStatement?クラスはステートメントクラスとは違って、インスタンスの生成時にSQLの骨組みを指定してプリコンパイルを行います。
そのため、テーブルに100件のレコードをINSERTする場合のように、同じようなSQLを繰り返して実行する場合は、PreparedStatement?クラスを使った方が高速に処理を行うことができます。
また、型変換や特殊文字のエスケープで説明した「'」のエスケープ処理を自動的に行ってくれるという長所も持っています。
ただし、実際に実行されるSQLを取得することができないので、実行されるSQLをログに出力したい場合などには使用できません。
また、動的に構造が変化するSQLの実行にも向いていません。

PreparedStatement?を使ったSQLの実行
以下に、PreparedStatement?を使ってSQLを実行するクラス「UsePreparedStatement?」のソースを示します。
基本的な流れは今までと同じですが、SQLの実行部分が異なります。
サンプルソース:UsePreparedStatement?.java

//==========================================================
//メソッド

  /**
   * mainメソッドです。<br>
   *
   * @param args コマンドライン引数
   * @exception Exception 例外
   */
  public static void main(String[] args) throws Exception
  {
    //コネクション
    Connection con = null;

    //PreparedStatement
    PreparedStatement preStm = null;

    try
    {
      //==========================================================
      //コネクションの取得

    //***** JDBCドライバのロード ******* ・・・2
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合
      Class.forName("org.postgresql.Driver");

      //MySQLの場合
      //Class.forName("org.gjt.mm.mysql.Driver");

      //Oracleの場合
      //Class.forName("("oracle.jdbc.driver.OracleDriver");
    //**********************************

    //***** URLを指定してコネクションを取得 ******* ・・・3
      //(DBによってこの記述は異なります)

      //PostgreSQLの場合、第1引数のURLは("jdbc:postgresql://ホスト名:ポート番号/DB名")になります
      //ユーザ名とパスワードが必要な場合は、このように第2引数, 第3引数に指定します
      con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/kensyu", "user01", "user01");

      //MySQLの場合、第1引数のURLは("jdbc:MySQL://ホスト名:ポート番号/DB名")になります
      //con = DriverManager.getConnection("jdbc:MySQL://localhost:5432/kensyu", "user01", "user01");            

      //Oracle JDBC OCI Driverの場合、第1引数のURLは("jdbc:oracle:oci:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:oci:@SID", "user01", "user01");

      //Oracle JDBC Thin Driverの場合、第1引数のURLは("jdbc:oracle:thin:@ホスト名:ポート番号:接続するデータベースのSID")になります
      //con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:SID", "user01", "user01");
    //*********************************************

      //自動コミットをOFFにする ・・・4
      //(デフォルトではONになっているので、必ずこのようにしてOFFにすること)
      con.setAutoCommit(false);

      //==========================================================
      //SQLの実行

      //コネクションからPreparedStatementを取得
      //(値を設定する場所には、「?」を指定しておきます)
      preStm = con.prepareStatement("INSERT INTO sampletable(pk, textcolumn, numbercolumn) VALUES(?, ?, ?)");

      //1つ目の「?」に文字列の値を設定
      //(「'」で両端を囲む必要はありません)
      preStm.setString(1, "PreparedStatementで設定した主キーの値");

      //2つ目の「?」に文字列の値を設定
      //(「'」が含まれていても自動的にエスケープされます)
      preStm.setString(2, "PreparedStatementで設定した'テキスト'の値");

      //2つ目の「?」にintの値を設定
      preStm.setInt(3, 100);

      //SQLを実行して更新行数を取得
      int updateLine = preStm.executeUpdate();

      //更新行数を出力
      System.out.println("更新行数=" + updateLine);

      //==========================================================
      //コミット
      con.commit();

    }

    //==========================================================
    //例外処理
    catch(Exception e)
    {
      //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
      e.printStackTrace();

      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //ロールバック

      //コネクションがnullでない場合
      if(con != null)
      {
        try
        {
          //ロールバック
          con.rollback();
        }
        //ロールバック中に例外が発生した場合
        catch(SQLException se)
        {
          //本来はここでログ出力などの処理を行いますが、ここでは例外内容をそのまま出力しています
          se.printStackTrace();

        //catchした例外を投げなおす
        throw se;
        }
      }

      //catchした例外を投げなおす
      throw e;
    }

    //==========================================================
    //終了処理
    finally
    {
      //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
      //リソース解放

      //ステートメントがnullでない場合
      if(preStm != null)
      {
        //ステートメントを閉じる
        preStm.close();
      }

      //コネクションがnullでない場合
      if(con != null)
      {
        //コネクションを閉じる
        con.close();
      }
    }
  }

}

PreparedStatement?クラスの具体的な使用方法は、次のようになります。

  1. コネクションの prepareStatement() メソッドに骨組みのSQLを指定してインスタンスを生成する。
    • ステートメントクラスとは違って、インスタンスの生成時にSQLを指定します。
    • ここで指定するSQLはあくまでも骨組みで、具体的な値を設定する箇所には代わりに「?」を指定します。
      (ここで指定する「?」は、プレースホルダと呼ばれます。)
  2. setXXX() メソッドで具体的な値を設定する。
    • このメソッドの引数にはインデックスと実際の値を指定します。
      インデックスは「0」からではなく、「1」から始まるので注意して下さい。
    • メソッド名のXXXの部分には「String」, 「int」などの具体的な型の名前が入ります。
      不適切な型を指定した場合には例外が発生します。
    • setString() メソッドで、文字列を指定する場合は両端の「'」は自動的につけられるので、実装者がつける必要はありません。
      また、「'」のエスケープも自動的に行われます。
  3. executeUpdate() メソッド、または executeQuery() メソッドでSQLを実行します。
    • ステートメントクラスの場合と同様に、更新系のSQL(INSERT, UPDATE, DELETE)の場合には executeUpdate() メソッド, 参照系のSQL(SELECT)の場合には executeQuery() メソッドを使用します。
    • インスタンスの生成時にすでにSQLを指定しているので、ここでは引数を指定しません。

課題
上のソースを実行して、実際にデータベースが更新されることを確認してみましょう。

総合課題


Java勉強会カリキュラムに戻る


添付ファイル: filepgAdmin_18.png 332件 [詳細] filepgAdmin_17.png 292件 [詳細] filepgAdmin_15.png 310件 [詳細] filepgAdmin_13.png 312件 [詳細] filepgAdmin_12.png 358件 [詳細] filepgAdmin_11.png 338件 [詳細] filepgAdmin_10.png 312件 [詳細] filepgAdmin_09.png 291件 [詳細] filepgAdmin_07.png 328件 [詳細] filepgAdmin_16.png 181件 [詳細] filepgAdmin_14.png 335件 [詳細] filepgAdmin_08.png 322件 [詳細] filepgAdmin_06.png 344件 [詳細] filepgAdmin_05.png 394件 [詳細] filepgAdmin_04.png 334件 [詳細] filepgAdmin_03.png 353件 [詳細] filepgAdmin_02.png 357件 [詳細] filepgAdmin_01.png 346件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-02-03 (火) 00:31:53 (582d)