|
・JDBCとは?
|
|
今回から応用編に入ります。昨今の業務システムにおいてリレーショナルデータベース(以降 RDB)の利用は必須となっています。有名なところでORACLE、DB2、PostgreSQL,MySQL等の有料のものから無料のものまで
あります。JavaからこれらのRDBを操作するときの統一的なインターフェース(アクセス方法)を提供するAPIセットをJDBC(*1)と言います。現在JDBCには1.0から3.0までのバージョンがあり、サポートバージョンは各RDBベンダーにより違います。
なぜJDBCがあるのでしょうか、世の中のRDBは先述したように多種多様なものがあります。オブジェクト指向的に考えてみてください(*2)。世の中にいくつあるかもわからない、また今後いくつ増えるかわからないような沢山のRDBに対してそれぞれAPIを作る(ORACLE用API、DB2用API・・・・・等)ようなアプローチは、DBの利用者から見てもRDBベンダーからしても全く現実的ではありません。なぜならRDBに対してアクセスする部分が各RDB製品によってAPIや方法が全く違うとすれば、開発者はRDBが変わるたびに新しい方法を覚えなければいけません。そしてRDBベンダーは自社製品のオリジナルAPIの利用方法を事細かにマニュアル化しなければならずバージョンアップも大変になります。これを回避するためにJDBCが登場(*3)しました。
これまで習ったオブジェクト指向的な考え方でよく考えてみてください。通常このような統一的なアクセス方法が必要なときは、インターフェースを利用するのが一般的です。まさにJDBCは、そのほとんどがインターフェースで構成されたAPIセットです。インターフェースであると言うことは実体は定義されていないと言うことです。ではその実体は誰が定義しているのでしょうか?それは各RDBベンダーがJDBCドライバと言う形で実装し、配布しています。この構造を整理すると下図のようになります。
| 図:JDBCのメカニズム |
 |
つまり私たち開発者は、どのようなRDBであっても、個々のRDBの利用方法に関係なく、JDBCインターフェースセットを通して同じように利用することが可能(*4)と言うことです。
*1 JDBCという単語に意味はありません。以前はJavaDataBaseConectivityの略だと言われていましたが、サンマイクロシステムズによるとただの商標だそうです。
*2 普段からオブジェクト指向的な考え方の癖をつけておくことにより、特にJavaの世界においては非常に見通しが良くなります。
*3 もともとWindows上においては、Microsoft社のODBCと言うRDBアクセスのためのAPIセットが存在しました。JDBCはJavaにODBCの考え方を取り込んだものだと言われています。
*4 正確には、ほぼ同じようにというべきです。理由はJDBCが提供するのはRDBに対するアクセス方法であり、データの検索や更新のための命令(SQL文)ではないからです。SQL文は規格が策定されており、どのようなRDBであってもほぼ同じような構文で発行することができますが、RDBごとに関数や細かい文法が違い完全な互換性をもって利用することはできません。またJDBCの「実際のサポートレベル」にもばらつきがあり、同じJDBC2.0でもRDBによって利用できるメソッドに違いがあります。
|
・SELECT文の発行
DriverManager、Connection、Statement、ResultSetの学習 |
|
では実際にはどのように利用するのでしょうか?いわゆるJDBCというのは「java.sql.*」で表現されるパッケージ群に含まれるので利用するときは適宜インポートする必要があります。早速下記のサンプルコードを見て下さい。
|
例:単純なJDBCのサンプル
|
| |
|
|
| |
package jp.co.wownet.education.jdbc;
import java.lang.*;
import java.util.*;
import java.sql.*;
/**
JDBCオートコミットバージョンSELECT文サンプル
*/
public class jdbc01{
public static void main(String args[]){
//ドライバークラスの設定
String JDBC_DRIVER = "oracle.jdbc.driver.OracleDriver";
//データベースののURLの設定
String DB_URL = "jdbc:oracle:thin:@192.168.1.14:1521:orcl";
//ユーザー名の設定
String DB_USER = "scott";
//パスワードの設定
String DB_PASS = "tiger";
//コネクション
Connection con = null;
//ステートメント
Statement st = null;
//結果セット
ResultSet rs = null;
//Loading Driver
try{
//JDBCドライバをVMにロードします。
Class.forName( JDBC_DRIVER );
//データベースへのコネクションを取得します。
con = DriverManager.getConnection( DB_URL,DB_USER,DB_PASS
);
//ステートメントオブジェクトを取得します。
st = con.createStatement();
//SQL文の実行・ResultSetの取得
rs = st.executeQuery("select EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
from EMP");
while( rs.next() ){//カーソルをひとつ進める。もし次があればtrue、なければfalse
System.out.println("\n----------------------\n");
System.out.println("EMPNO > " + rs.getString(1)
);//rs.getString("EMPNO")というやり方も可能
System.out.println("ENAME > " + rs.getString(2)
);
System.out.println("JOB > " + rs.getString(3)
);
System.out.println("MGR > " + rs.getString(4)
);
System.out.println("HIREDATE > " + rs.getString(5)
);
System.out.println("SAL > " + rs.getString(6)
);
System.out.println("COMM > " + rs.getString(7)
);
System.out.println("DEPTNO > " + rs.getString(8)
);
System.out.println("\n----------------------\n");
}
rs.close();
st.close();
}catch( Exception e ){
//スタックトレース
e.printStackTrace();
}finally{
//いかなるときでも必ずクローズ!!
try{
if( con!=null ) con.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
}
|
|
| |
|
|
|
これはORACLEのサンプルデータベースである「orcl」にユーザー名「scott」、パスワード「tiger」で接続して「EMP」テーブルに対してSELECT文を発行し、その結果を取得しています。この時重要なクラスは以下の4つです。
|
表:JDBCにおいて重要なクラス、パッケージはjava.sql.*に属しています。
|
| DriverManager |
JDBCのドライバを管理するクラスです。このクラスを通してJDBCドライバを操りRDBへの接続をつかさどるConnectionオブジェクトを取得します。 |
| |
|
| Connection |
RDBへの接続(セッション)を管理するクラスです。Connectionインスタンスを取得した時点でRDBと接続されています。ここからSQLコンテナと呼ばれるStatementオブジェクトを取得します。RDBの利用が終了したら必ず切断(クローズ)してください。 |
| |
|
| Statement |
一般にSQLコンテナと呼ばれるSQL文を発行するためのクラスです。Statementインスタンスのexecute等のメソッドの引数としてSQL文を渡すことにより、RDBに対してSQLが発行されます。executeQueryなどのクエリーの場合は結果セット(ResultSet)がリターンされてきます。またあらかじめSQL文をパラメータを除いて事前に整形しておくPreparedStatementクラスというものもあります。PreparedStatementはStatementを継承したクラスで、通常はこちらの利用が一般的です。 |
| |
|
| ResultSet |
結果セットと呼ばれ、クエリーの結果を内部に持つイテレーターの一種です。nextメソッド(次があればtrue、なければfalse)によりカーソルを次へ進め(次のレコード)、get系のメソッドにより各カラムのデータを取得します。 |
|
実際にRDBに接続するための手順は以下のようになります
- 任意のJDBCドライバのVM内へのロード(ダイナミッククラスローディング)
→ Class.forName( JDBC_DRIVER )
- DriverManagerによりConnectionの取得(RDBへの接続)
→ con = DriverManager.getConnection(
DB_URL,DB_USER,DB_PASS )
- ConnectionからのStatementの取得(SQLコンテナの生成)
→ st = con.createStatement()
- 場合によって、戻り値としてResultSetの取得、および結果のイテレーション(クエリ結果の取得)
→ rs = st.executeQuery("select
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO,ADDRESS,POINT from
EMP")
→ while( rs.next()
){ ・・・ }
- 基本的にはResultSet、Statement、Connectionの順(2〜4と逆順序)で接続をクローズしていきます。通常の実装であればConnectionのクローズのみで問題はありませんが、実装レベルにより変わるので既知のRDBでない限りは順次クローズ処理していくほうが安全です。またどんな場合でもConnectionはクローズしなければならないのでfinallyブロック内でクローズ処理してください。
→ rs.close()
→ st.close()
→ if( con!=null )
con.close()
それでは上記手順を説明していきましょう。
JDBCドライバのVM内へのロードとは、いわゆるダイナミッククラスローディングと呼ばれるものでクラスを表す文字列からインスタンスを取得したり、クラスをVM内へロード(例えばスタティックメンバーなどを使用可能な状態へ移行)します。このとき引数に指定する文字列は、RDBベンダーが提供するJDBCのドライバクラス名をフルパス(例:oracle.jdbc.driver.OracleDriver)で記述します。こうしてVM内にロードされたドライバをDriverManagerを通して操作し、このDriverManagerのスタティックなメソッドであるgetConnectionを利用してRDBへの接続を確保します。このときにgetConnectionの引数として渡しているのは以下の3つです。
- JDBC-URL
→ jdbc:[subprotocol]:[subname]
で表される文字列です。[subprotocol]はRDBにより違うが、RDBの接続先情報(IPやマシン名、ポートなど)を記述することが多いようです。[subname]
は[subprotocol]により変わるようですが一般的にデータベースの名前であることが多いようです。上記のサンプルコードでは「jdbc:oracle:thin:@192.168.1.14:1521:orcl」となっています。
- ユーザーID、パスワード
→JDBC-URLで表されるRDBへ接続する際のユーザーID、パスワード
これらの情報によりRDBへの接続が成功するとConnectionオブジェクトが取得できます。このConnectionオブジェクトはRDBへの接続を表すオブジェクトで、クローズされない限り何度でもSQLコンテナ(StatementやPreparedStatement)を生成することができます。ConnectionオブジェクトからcreateStatementメソッド(もしくはprepareStatement)によりStatementオブジェクトを生成します。このStatementオブジェクトはConectionと同様にクローズされない限り何度でもSQLを発行することができます。取得したStatementオブジェクトはexecuteQuery、executeUpdate、executeなどのメソッドを持ちその引数にSQL文を渡してやることによりRDBに対してSQLを実行します。そして今回の場合は、executeQueryであるため結果セット(ResultSet)オブジェクトがリターンされてきます。ResultSetオブジェクトは先述したようにイテレータの一種のためwhile文を用いて検索結果の全てのレコード情報を取得することができます。下図はResultSetのモデル図です。
サンプル用のソースファイル(JavaDoc)
サンプルのコンパイル、実行方法
(Win)
javac jp\co\wownet\education\jdbc\jdbc01.java
もしくは javac -d . jdbc01.java
java -classpath .;classes12.zip;nls_charset12.zip jp.co.wownet.education.jdbc.jdbc01
(UNIX系)
javac jp/co/wownet/education/jdbc/jdbc01.java
もしくは javac -d . jdbc01.java
java -classpath .:classes12.zip:nls_charset12.zip jp.co.wownet.education.jdbc.jdbc01
|
| ・INSERT、UPDATE、DELETE文の発行とコミットモード |
|
前回はSELECT文の発行を通してJDBCを利用しました。今回はDBの情報を変更する動作を行ってみましょう。前回と同じくSQLの発行にはStatementオブジェクトを用いますが、DBに対して変更を加えるSQL命令(INSERT、UPDATE、DELETE等)のばあいは発行時にexcuteUpdateメソッドを利用します。このexcecuteUpdateメソッドの戻り値はint型でレコードの更新件数です。下記からDBに対してインサートを行うサンプルコードをダウンロードして実行してみてください。
サンプル用のソースファイル(JavaDoc)
通常の更新や検索はもう大丈夫だと思います。では仮に
10件のレコードをインサートしている時、5件目のデータでプログラムが中断されてしまった。10件そろってインサートしなければいけないので、最初に成功した4件は削除しなければならなくなった。できれば、まとめてインサートすべきデータの途中で失敗したときには全件の入力をキャンセルするようにして、今後このような事故を無くしたい。
という状況を考えてみましょう。起きてしまったものはどうしようにもありませんが、次回からそのような事故を防ぐためにはコミットモードの利用が有効的です。JDBCにおけるコミットはSQLにおけるコミットと全く同じ意味だと思ってください。通常DriverManagerから取得したConnectionオブジェクトはAutoCommitモードが有効になっています。そのためexcuteUpdateしたSQLは、発行と同時にDBに対して変更が反映されます。これでは上記の失敗したときの対策が取れないのでConectionオブジェクトのAutoCommitモードを無効にしてマニュアルコミットにしましょう。方法はConnectionオブジェクトのsetAutoCommitメソッドの引数をfalseにして実行するだけです。これでAutoCommitは無効になり、変更をDBに反映させるときにはコミットしなければなりません。コミットの方法は、同じくConnectionオブジェクトのcommitメソッドを実行することにより行われます。また変更をキャンセルするロールバックはrollbackメソッドを利用してください。下記のマニュアルコミットでのINSERT文を実行するサンプルコードを見て下さい。
|
例:マニュアルコミットのサンプル
|
| |
|
|
| |
package jp.co.wownet.education.jdbc;
import java.lang.*;
import java.util.*;
import java.sql.*;
/**
JDBCマニュアルコミットバージョンINSERT文サンプル
*/
public class jdbc03{
public static void main(String args[]){
//ドライバークラスの設定
String JDBC_DRIVER = "oracle.jdbc.driver.OracleDriver";
//データベースののURLの設定
String DB_URL = "jdbc:oracle:thin:@192.168.1.14:1521:orcl";
//ユーザー名の設定
String DB_USER = "scott";
//パスワードの設定
String DB_PASS = "tiger";
//コネクション
Connection con = null;
//ステートメント
Statement st = null;
//Loading Driver
try{
//JDBCドライバをVMにロードします。
Class.forName( JDBC_DRIVER );
//データベースへのコネクションを取得します。
con = DriverManager.getConnection( DB_URL,DB_USER,DB_PASS
);
//マニュアルコミットモードにセット
con.setAutoCommit(false);
//ステートメントオブジェクトを取得します。
st = con.createStatement();
//SQL文の実行・更新結果の取得
int i = st.executeUpdate("insert into emp(EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO)
values(2,'SEITARO','PG&SE',0,'02-11-18',1750,100,10)");
System.out.println(i + "件の更新を行いました。");
//ステートメント
st.close();
//コミット(確定処理)
//もし、コミットにいたる前に問題が起きた場合は、この部分の処理は行われず
//catchブロック内に処理が移る。そしてロールバック処理が実行され、インサート
//はキャンセルされる。
con.commit();
}catch( Exception e ){
//重要!問題が発生したときにはロールバック
try{
if( con!=null )con.rollback();
}catch(SQLException se){
se.printStackTrace();
}
//スタックトレース
e.printStackTrace();
}finally{
//いかなるときでも必ずクローズ!!
try{
if( con!=null ){
//オートコミットモードに再セット
con.setAutoCommit(true);
con.close();
}
}catch(SQLException se){
se.printStackTrace();
}
}
}
} |
|
| |
|
|
|
このばあいまず、Connectionを取得した時点でAutoCommitを解除しています。その後Statementを取得しexecuteUpdateし、全てが正常に実行されればcommitメソッドの実行までが行われます。もし実行時に何らかの問題が発生した場合には、処理はcatchブロック内部に移行しロールバックが実行されそれまでのインサート処理はキャンセルされます。そして最後にfinallyブロックに入りAutoCommitモードを有効にしコネクションをクローズしています。このようにすれば、もし仮にインサートに失敗したとしてもDBにゴミレコードを残す心配はありません。また最後にAutoCommitモードを有効にしている理由は、今後コネクションプーラーなどを利用するときのための練習です。
サンプル用のソースファイル(JavaDoc)
|
| ・PreparedStatementの利用 |
|
いままで勉強してきたStatementによるSQLの発行は毎回毎回1からSQL文を合成しなければならないし、SQL文を合成するときにシングルクォーテーションなどの位置にも気をつけながら本文と可変の値をStringとして交互に結合していました。少し考えただけでもこのSQL文の合成が非常に手間がかかり、生産性も低く、バグをはらみやすいやり方であることは理解できます。なんと言っても後にロジック部分の見直しをしたときにSQLの合成部分が非常に醜くなり、コードの見通しが悪くなります。これは一見するとコードが綺麗か汚いかの話のように聞こえますが、コードの管理や仕変に対する強度に大きく影響します。これを解決する手法の一つとしてPreparedStatementの利用が挙げられます。簡単に言ってしまうと、SQLで値として扱われる部分を「?」で記述し、利用するときに「?」の部分を実際の値と入れ替えてしまうものです。利用法は下記のような順番になります。
- ConnectionオブジェクトからprepareStatementメソッドで引数にパラメータ置換文字「?」を入れたSQL文を渡しPreparedStatementオブジェクトを取得
→例 :pst = con.prepareStatement("select
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO,ADDRESS,POINT from
EMP where empno=?");//最後のempnoの条件が?になっている
- PreparedStatementオブジェクトのset系メソッドにより値を「?」の順番に従ってセット
→例:pst.setString(1,"1");//一つ目の?に文字列"1"をセットする
- Statementと同様にexecute系のメソッドを発行、ただしその際にメソッドの引数はない
下記のコードを参照してください。赤字のコメントが記述されている部分が該当部分です。setStringとsetIntどちらでも構わないと記述してありますが、これはオラクルの場合です。他のRDBを利用する際には調査した上で利用してください。
|
例:PreparedStatementのサンプル
|
| |
|
|
| |
package jp.co.wownet.education.jdbc;
import java.lang.*;
import java.util.*;
import java.sql.*;
/**
JDBCオートコミットバージョンPreparedStatementによるSELECT文サンプル
*/
public class jdbc04{
public static void main(String args[]){
//ドライバークラスの設定
String JDBC_DRIVER = "oracle.jdbc.driver.OracleDriver";
//データベースののURLの設定
String DB_URL = "jdbc:oracle:thin:@192.168.1.2:1521:orcl";
//ユーザー名の設定
String DB_USER = "scott";
//パスワードの設定
String DB_PASS = "tiger";
//コネクション
Connection con = null;
//プリペアドステートメント
PreparedStatement pst = null;
//結果セット
ResultSet rs = null;
//Loading Driver
try{
//JDBCドライバをVMにロードします。
Class.forName( JDBC_DRIVER );
//データベースへのコネクションを取得します。
con = DriverManager.getConnection( DB_URL,DB_USER,DB_PASS
);
//プリペアドステートメントを取得します。
pst = con.prepareStatement("select EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO,ADDRESS,POINT
from EMP where empno=?");
//パラメーターのセット
pst.setString(1,"1");
//pst.setInt(1,1);//これでも可
//SQL文の実行・ResultSetの取得
rs = pst.executeQuery();
while( rs.next() ){//カーソルをひとつ進める。もし次があればtrue、なければfalse
System.out.println("\n----------------------\n");
System.out.println("EMPNO > " + rs.getString(1)
);//rs.getString("EMPNO")というやり方も可能
System.out.println("ENAME > " + rs.getString(2)
);
System.out.println("JOB > " + rs.getString(3)
);
System.out.println("MGR > " + rs.getString(4)
);
System.out.println("HIREDATE > " + rs.getString(5)
);
System.out.println("SAL > " + rs.getString(6)
);
System.out.println("COMM > " + rs.getString(7)
);
System.out.println("DEPTNO > " + rs.getString(8)
);
System.out.println("ADDRESS > " + rs.getString(9)
);
System.out.println("POINT > " + rs.getString(10)
);
System.out.println("\n----------------------\n");
}
rs.close();
pst.close();
}catch( Exception e ){
//スタックトレース
e.printStackTrace();
}finally{
//いかなるときでも必ずクローズ!!
try{
if( con!=null ) con.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
} |
|
| |
|
|
|
PreparedStatementを利用するときに注意しなければならないのが、やはりドライバによるサポートレベルです。サポートレベルの低いドライバだとSQL文によってPreparedStatementが利用できない場合があったりしますので十分に注意した上で利用してください。下記にいくつかのサンプルコードを掲示します。
サンプル用のソースファイル(JavaDoc)
| jdbc04.java |
JDBCオートコミットバージョンPreparedStatementによるSELECT文サンプル |
| jdbc05.java |
JDBCオートコミットバージョンPreparedStatementによるINSERT文サンプル |
| jdbc06.java |
JDBCマニュアルコミットバージョンPreparedStatementによるINSERT文サンプル |
| jdbc07.java |
JDBCマニュアルコミットバージョンPreparedStatementによるDELETE文サンプル |
|
| |
|