prepareStatementの使用

広告

「PreparedStatement」は「Statement」と同じくデータベースに対するSQL文を実行するために使われますが、「PreparedStatement」で使うSQLは事前にコンパイルが行われています。その為高速に実行することが出来るようになっています。

ただ全てを事前にコンパイルしてしまっては同じ問い合わせにしか利用出来ません。そこでSQLの中でも変更が行われる部分を後からパラメータとして指定することが出来るようになっています。

「PreparedStatement」は例えば大量のINSERTを実行するような場合など基本的に同じSQL文で挿入する値の部分だけが違うようなSQLを実行する場合に便利なステートメントになっています。

PreparedStatementの作成

プリペアドステートメントは「java.sql.PreparedStatement」インターフェースのオブジェクトですが、作成するにはコネクションから作成します。具体的には「Connection」インターフェースで定義されている"prepareStatement"メソッドを使います。

パラメータ付き SQL 文をデータベースに送るための PreparedStatement オブジェ
クトを作成します。

返される PreparedStatement オブジェクトを使って作成された結果セットは、デ
フォルトでは、TYPE_FORWARD_ONLY の型で、CONCUR_READ_ONLY の並行処理レベル
を持ちます。 

パラメータ:
  sql - 1 つ以上の '?' IN パラメータプレースホルダーを含めることがで
    きる SQL 文 
戻り値:
  プリコンパイルされた SQL 文を含む新しいデフォルトの PreparedStatement
    オブジェクト 
例外: 
  SQLException - データベースアクセスエラーが発生した場合

パラメータとして後から指定する部分には、実際の値を記述する替わりに「?」を記述したSQL文を作成し、そのSQLを元にプリペアドステートメントを作成します。

String sql = "insert into kabukatable (code, company) values (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

この例では実際に挿入する値を記述箇所に「?」を使っています。2箇所使っていますので、後からパラメータを2つ指定する必要があります。

パラメータへ値の設定

次にプリペアドステートメントオブジェクトに対して、パラメータに設定する実際の値を指定します。これには「PreparedStatement」インターフェースで用意されている「setInt」メソッドを使います。

指定されたパラメータを指定された Java の int 値に設定します。データベー
スに送るときに、ドライバはこれを SQL INTEGER 値に変換します。 

パラメータ:
  parameterIndex - 最初のパラメータは 1、2 番目のパラメータは 2、...
   となる
  x - パラメータ値 
例外: 
  SQLException - データベースアクセスエラーが発生した場合

パラメータの数だけメソッドを実行します。パラメータはSQL文中に記述された「?」を先頭から順番に1,2,3、・・・と順番に番号が割り当てられています。1番目のメソッドにはパラメータの番号を指定し、2番目の引数にパラメータに設定したい値を記述します。

String sql = "insert into kabukatable (code, company) values (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

pstmt.setInt(1, 4755);
pstmt.setString(2, "楽天(株)");

パラメータに値を設定するには設定する値のデータ型毎に別々のメソッドが用意されています。例えば次のようなメソッドがあります。

メソッド説明
void setString(int parameterIndex, String x)String 値に設定します。
void setInt(int parameterIndex, int x)int 値に設定します。
void setLong(int parameterIndex, long x)long 値に設定します。
void setByte(int parameterIndex, byte x)バイト配列に設定します。
void setFloat(int parameterIndex, float x)float 値に設定します。
void setDouble(int parameterIndex, double x)double 値に設定します。
void setDate(int parameterIndex, Date x)java.sql.Date 値に設定します。
void setTime(int parameterIndex, Time x)java.sql.Time 値に設定します。
void setBoolean(int parameterIndex, boolean x)boolean 値に設定します。
void setTimestamp(int parameterIndex, Timestamp x)java.sql.Timestamp 値に設定します。

設定するデータ型に合わせてメソッドを選択して下さい。

なお、MySQLの場合に文字列を使う場合は「'楽天(株)'」のようにシングルクオーテーション(')で括って記述していましたが、プリペアドステートメントの「setString」メソッドを使う場合には、自動的にデータベースで必要なエスケープ処理をしてくれます。

プリペアドステートメントの実行

プリペアドステートメントを作成したら、SQL文を実行します。「PreparedStatement」インターフェースにはSELECTなどのクエリーの結果を取得する場合に使う「executeQuery」メソッドと、INSERTやUPDATEなどの結果が返されない場合に使う「executeUpdate」メソッドがあります。この使い方は「Statement」の場合と同じです。

実際の使い方は次のようになります。

Connection conn = null;

try {
  conn = DriverManager.getConnection(url, user, password);

  String sql = "insert into kabukatable (code, company) values (?, ?)";
  PreparedStatement pstmt = conn.prepareStatement(sql);
  pstmt.setInt(1, 4755);
  pstmt.setString(2, "楽天(株)");

  int num = pstmt.executeUpdate();
}catch (SQLException e){
  out.println("SQLException:" + e.getMessage());
}

パラメータのクリア

プリペアドステートメントを複数回続けて実行する場合、値が変更されないパラメータについては改めて設定しなくても構いません。

例えば次の例では1番目のパラメータは変更が無いので、2回目に実行する時には2番目のパラメータだけを設定しています。

Connection conn = null;

try {
  conn = DriverManager.getConnection(url, user, password);

  String sql = "insert into kabukatable (basho, company) values (?, ?)";
  PreparedStatement pstmt = conn.prepareStatement(sql);

  pstmt.setInt(1, "東証");
  pstmt.setString(2, "ドコモ");
  int num = pstmt.executeUpdate();

  pstmt.setString(2, "KDDI");
  num = pstmt.executeUpdate();
}catch (SQLException e){
  out.println("SQLException:" + e.getMessage());
}

それに対して、現在設定されているパラメータを全て一度クリアしたい場合には「PreparedStatement」インターフェースで用意されている「clearParameters」メソッドを使います。

現在のパラメータ値をすぐにクリアします。 

通常、文を繰り返し使用するために、パラメータ値は強制的に残されます。パラ
メータ値を設定すると、前の値は自動的にクリアされます。しかし、現在のパラ
メータ値によって使用されたリソースをただちに解放したほうが役に立つ場合が
あります。これは、メソッド clearParameters を呼び出して実行することがで
きます。 

例外: 
  SQLException - データベースアクセスエラーが発生した場合

パラメータを残すような利用を行う場合、予期せぬ値が残ってしまうようなケアレスミスなどが発生しやすくなります。明確にクリアしたい場合にはこのメソッドを利用して下さい。

サンプルプログラム

では簡単なサンプルプログラムで試してみましょう。

web.xmlファイルは下記のようにしました。

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

  <servlet>
    <servlet-name>databasetest</servlet-name>
    <servlet-class>DatabaseTest6</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>databasetest</servlet-name>
    <url-pattern>/databasetest</url-pattern>
  </servlet-mapping>
</web-app>

プログラムは下記の通りです。

DatabaseTest6.java

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;

public class DatabaseTest6 extends HttpServlet {
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{

    response.setContentType("text/html; charset=Shift_JIS");
    PrintWriter out = response.getWriter();

    out.println("<html>");
    out.println("<head>");
    out.println("<title>データベーステスト</title>");
    out.println("</head>");
    out.println("<body>");

    Connection conn = null;
    String url = "jdbc:mysql://localhost/jdbctestdb";
    String user = "testuser";
    String password = "testpass";

    try {
      Class.forName("com.mysql.jdbc.Driver").newInstance();
      conn = DriverManager.getConnection(url, user, password);

      String sql = "insert into kabukatable (code, company) values (?, ?)";
      PreparedStatement pstmt = conn.prepareStatement(sql);

      pstmt.setInt(1, 9437);
      pstmt.setString(2, "(株)NTTドコモ");
      int num = pstmt.executeUpdate();

      pstmt.setInt(1, 9433);
      pstmt.setString(2, "KDDI(株)");
      num = pstmt.executeUpdate();

      pstmt.close();

      sql = "select * from kabukatable";
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery(sql);

      while(rs.next()){
        int code = rs.getInt("code");
        String company = rs.getString("company");
        out.println("<p>");
        out.println("コード:" + code + ", 会社名:" + company);
        out.println("</p>");
      }

      rs.close();
      stmt.close();
    }catch (ClassNotFoundException e){
      out.println("ClassNotFoundException:" + e.getMessage());
    }catch (SQLException e){
      out.println("SQLException:" + e.getMessage());
    }catch (Exception e){
      out.println("Exception:" + e.getMessage());
    }finally{
      try{
        if (conn != null){
          conn.close();
        }
      }catch (SQLException e){
        out.println("SQLException:" + e.getMessage());
      }
    }

    out.println("</body>");
    out.println("</html>");
  }
}

上記をコンパイル後に「d:¥servlet-sample¥database¥WEB-INF¥classes¥」ディレクトリにクラスファイルを移動した後で、ブラウザで「http://localhost:8080/database/databasetest」へアクセスしてみます。

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

今回は2つの行を追加して後で全データを取り出して表示しました。

( Written by Tatsuo Ikura )