PreparedStatement

3.2 PreparedStatement

 今までのプログラムでは、Statementを利用しSQL文を実行していました。JDBCには、効率よくSQLを実行するためにStatementを拡張したPreparedStatementが存在します。本節では、PreparedStatementについて学習していきます。

3.2.1 PreparedStatementとは

 PreparedStatementは、前節で学習したStatementと同じSQL文をデータベースへ送信するための準備を行う機能の1つです。実行するSQL文を先に解析しておくことで、処理速度の向上を図ります。

 StatementとPreparedStatementの違いは以下に示す通りになります。

 ・ Statement
  ① SQL文を受け取って解析し、すぐに実行します。

 ・ PreparedStatement
  ① SQL文を受け取って解析し、値があればいつでも実行できる状態にします。
  ② SQL文に必要な値をセットします。
  ③ SQL文を実行します。

 上記の手順を比べると、手順が増えたPreparedStatementの方が、手間がかかって遅くなる印象を受けると思います。PreparedStatementの最大の特徴はSQL文の解析と実行の処理を別々に行う点で、これによって処理速度を向上させることができます。1回のSQLを実行する場合には、処理速度はほとんど変わることはありませんが、検索条件や更新する値のみが異なるSQL文を大量に実行する場合、実行する度にSQL文の解析を行うStatementと、初めの1回しかSQL文の解析を行わないPreparedStatementとでは処理速度に大きな違いが出てきます。

3.2.2 PreparedStatementを利用したデータベースアクセスの基本構文

 PreparedStatementを利用した処理の流れは次のようになります。

 ① JDBC(java.sqlパッケージ)をインポートする。
 ② JDBCドライバを読み込む。
 ③ データベースへ接続する。
 ④ 基準となるSQL文を作成する。
   String sql = “UPDATE bookinfo SET price= ? WHERE isbn= ?";
 ⑤ SQL文をデータベースに送るための準備を行う。
   PreparedStatement ps = con.prepareStatement(sql);
 ⑥ パラメータ変数に値をセットする。
   ps.setInt(パラメータのインデックス, パラメータ値);
   ps.setString(パラメータのインデックス, パラメータ値);

 ⑦ SQL文をデータベースへ送信し、結果を受け取る。
   int rowsCount = ps.executeUpdate(); //更新系
   ResultSet rs = ps.executeQuery(); //検索系

 PreparedStatementを利用する場合、基準となるSQL文を作成します。SQL文は、動的に変化する値の部分に「?」(はてなマーク)を使用します。この「?」(はてなマーク)のことをパラメータ変数やプレースホルダーと呼びます。また、⑤の処理ではSQLをデータベースに送る準備を行う際に、createStatement()メソッドの代わりにprepareStatement()メソッドを利用します。この時、引数に渡されたSQL文を解析をします。⑥の処理では、SQL文のパラメータ変数に値をセットしています。int型の値をセットする場合には「setInt()メソッド」、String型の文字列をセットする場合には「setString()メソッド」を利用します。⑦の処理で、SQL文をデータベースへ送信し実行します。⑤の時点でSQL文を渡されているので、executeUpdate()メソッドの引数にSQL文を渡す必要はありません。

 では、実際にPreparedStatementを利用したプログラムを作成してみましょう。

PreparedStatementを利用したプログラム

 このプログラムは、PreparedStatement を利用してデータの更新を行うプログラムです。

① ソース・フォルダー      :myjdbc_kanda/src
② パッケージ          :jp.co.f1.jdbc.ch03
③ 名前             :SamplePrepared
④ 作成するメソッド・スタブの選択:public static void main(String[] args) にチェックを入れる

➢ SamplePrepared.java
package jp.co.f1.jdbc.ch03;

import java.sql.*;

public class SamplePrepared {

	private static String RDB_DRIVE="org.mariadb.jdbc.Driver";
	private static String URL="jdbc:mariadb://localhost/mybookdb";
	private static String USER="bms";
 	private static String PASSWD="bms123";
 
 	public static void main(String[] args) {
 
 		String sql = null;
 		int num = 0;
 		Connection con = null;
 		PreparedStatement ps = null;
 
 		try {
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			sql = "UPDATE bookinfo SET price=? WHERE isbn=?";
 
 			System.out.println("■更新前の書籍一覧を表示");
 			selectAll();
 
 			ps = con.prepareStatement(sql);
 			ps.setInt(1, 3000);
 			ps.setString(2,"00001");
 			num = ps.executeUpdate();
 			System.out.println("\n" + num + "件データを更新しました。\n");
 
 			System.out.println("■更新後の書籍一覧を表示");
 			selectAll();
 		}catch(Exception e){
 			System.out.println("JDBCデータベース接続エラー");
 		}finally{
 			if(ps != null){
 				try{ps.close();}catch(SQLException ignore){}
 			}
 			if(con != null){
 				try{con.close();}catch(SQLException ignore){}
 			}
 		}
 	}
 
 	private static void selectAll(){
 		Connection con = null;
 		PreparedStatement ps = null;
 
 		try{
 			Class.forName(RDB_DRIVE);
 			con = DriverManager.getConnection(URL,USER,PASSWD);
 
 			String sql = "SELECT * FROM bookinfo WHERE isbn LIKE ?";
 
 			ps = con.prepareStatement(sql);
 			ps.setString(1, "000%");
 			ResultSet rs = ps.executeQuery();
 			while (rs.next()) {
 				System.out.println("isbn -> "   + rs.getString("isbn") +
 						"\t title -> " + rs.getString("title") +
 						"\t price-> "  + rs.getInt("price"));
 			}
 		}catch (Exception e) {
 			System.out.println("JDBCデータベース接続エラー"+e);
 		}finally{
 			if(ps != null){
 				try{ps.close();}catch(SQLException ignore){}
 			}
 			if(con != null){
 				try{con.close();}catch(SQLException ignore){}
 			}
 		}
 	}
 
 }

実行結果

解説

 このプログラムの大きな構成として、12行目から46行目でデータの更新処理を記述したmain()メソッドを定義しています。更に48行目から76行目ではデータを表示するselectAll()メソッドを定義しています。この2つのメソッドではUPDATE処理とSELECT処理を行なっていますが、どちらもPreparedStatementを利用してSQL文の実行を行なっています。

 PreparedStatementを利用するために23行目でパラメータ変数を用いたSQL文を定義しています。「?」(はてなマーク)の部分が動的に変えることのできるパラメータ変数になっています。それ以外の部分は、実行する度に使い回される定型文となります。
   23: sql = "UPDATE bookinfo SET price=? WHERE isbn=?";

 28行目でprepareStatement()メソッドの引数にSQL文を渡しています。この時SQL文の解析が行われます。
   28: ps = con.prepareStatement(sql);

 29行目と30行目でSQL文の「?」(はてなマーク)で記述されていたパラメータ変数に値をセットしています。
   29: ps.setInt(1, 3000);
   30: ps.setString(2,"00001");

 各パラメータ変数にメソッドを利用して値をセットするため、パラメータ変数の数だけメソッドを実行します。このプログラムでははてなマークが2つあるため、メソッドを2回実行しています。
 なお、値をセットするメソッドには引数が2つあり、第1引数にはパラメータのインデックス、第2引数にはパラメータ変数にセットする値を記述します。そのため、「ps.setInt(1, 3000);」は「1個目のはてなマークに3000をセットする。」という処理になり、「ps.setString(2, “00001”);」は「2個目のはてなマークに”00001″をセットする。」という処理になります。

 このSQL文を作成する手順は以下のようになります。

 SQL文の中に記述された「?」に対して、先頭から順番に1、2、3・・・と番号が割り当てられます。このプログラムではpriceの値が1番、isbnの値が2番になります。

図 3.1.3初期データとSQL実行後のデータ

 次に、セットメソッドの第一引数の値に対応する「?」に第二引数の値をセットします。

図 3.2.2 パラメータ変数への値の設定

 その結果、以下のSQL文が生成され、実行されます。
   UPDATE bookinfo SET price = 3000 WHERE isbn='00001';

 実行結果を確認すると、isbnが00001のデータのpriceが変更されているのが確認できます。

 更にselectAll()メソッド内でも同様の処理が行われています。
 56行目で基準となるパラメータ変数を用いたSQL文定義しています。
   56: String sql = "SELECT * FROM bookinfo WHERE isbn LIKE ?";

 59行目でSQLのパラメータ変数に値をセットしています。
   59: ps.setString(1, "000%");

 この処理でSQL文のLIKEの後ろのはてなマークに値がセットされるため、次のようなSQL文になります。    SELECT * FROM bookinfo WHERE isbn LIKE '000%';

ポイント
  • PreparedStatementを利用するとSQL文を予め解析しておき、使いまわすことができるため処理速度の向上を図ることができる。
  • PreparedStatementを利用する場合のSQL文は値の部分をパラメータ変数と呼ばれる「?」(はてなマーク)で記述する。
  • パラメータ変数に値をセットする場合、セットメソッドの引数にパラメータ変数のインデックスと値を記述する。
StatementとPreparedStatementの処理のタイミング

 以下の図はStatementとPreparedStatementを利用した処理の流れをまとめた図です。
 簡単に目を通しておきましょう。


NEXT>> 3.3 本章のまとめ