【Java】JDBCを利用し、データベースに接続、操作する
JavaにはJDBCというデータベース(以下DB)へ接続するための仕組みがあります。
DBを利用すると、より大規模なデータ蓄積をSQLを使って行うことができます。
要はすっごいセーブデータですねw
データベースを準備する
Javaで利用可能なDBはたくさんあります。少なくとも下記のようなDBには対応しているでしょう。
・SQLite
・PostgreSQL
・MySQL
・Oracle Database
・Db2
・SQLServer
この中でお手軽に始めるのならSQLiteです。おススメは無料で使えて現場でも使われているPostgreSQL、MySQLです。
Oracle DatabaseやSQLServerなどは有料なので個人で使うことはあまりないですね。私も使ったことはありません。
自分が使用したいDBを使ってもらうと良いかと思います。
私がテストで使ったものはPostgreSQLです。
DBが決まったら環境にインストールしておきましょう。
JDBCライブラリを準備する
Javaで各データベースを利用するためのJDBCライブラリを準備します。
Oracle Database JDBC
SQLite JDBC
PostgreSQL JDBC
MySQL JDBC
Db2 JDBC
SQLServer JDBC
テストで利用するのはPostgreSQLなのでPostgreSQLのJDBCドライバーのjarをダウンロードしておきました。
接続
PostgreSQLのJDBC接続は下記のようなコードを記述します。
Connection connection = DriverManager.getConnection("jdbc:postgresql://接続先ホスト:接続先ポート/DB名",ユーザー名,パスワード);
DBをインストールしたときのユーザー名、パスワード、ポートなどの情報が必要です。
別サーバーに接続する場合は接続ホストの部分はIPアドレスにするなり修正してください。
DB名がない場合はユーザー名のDBに対してアクセスされます。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass)){
//接続情報を表示
System.out.println(connection);
}
}
}
org.postgresql.jdbc4.Jdbc4Connection@769c9116
接続に失敗した場合は例外が発生します。
SQL文を実行してみる
それでは早速インサート文を実行してデータを追加し、表示してみましょう。
まずはデータベースを作成します。
今回はこんな感じの簡単なテーブルを用意しました。
create table test( id integer primary key ,cd varchar(10) ,value text );
SQLを実行する場合はStatementオブジェクトを取得する必要があります。
StatementオブジェクトのexecuteUpdateメソッドでinsert文を実行できます。
それでは実際にデータを追加してみましょう。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass);
Statement statement = connection.createStatement();){
//insert文
StringBuilder sql = new StringBuilder();
sql.append("insert into test values");
sql.append("(1,'test','テスト')");
sql.append(",(2,'anime','アニメ')");
sql.append(",(3,'game','ゲーム');");
//SQL実行
statement.executeUpdate(sql.toString());
}
}
}
続いて追加したデータを表示してみましょう。
StatementオブジェクトのexecuteQueryメソッドでselect文を実行できます。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass);
Statement statement = connection.createStatement();){
//select文
StringBuilder sql = new StringBuilder();
sql.append("select * from test;");
//SQL実行
try(ResultSet result = statement.executeQuery(sql.toString())){
while(result.next()) {
int id = result.getInt("id");
String cd = result.getString("cd");
String value = result.getString("value");
System.out.printf("id=%d,",id);
System.out.printf("cd=%s,",cd);
System.out.printf("value=%s%n",value);
}
}
}
}
}
id=1,cd=test,value=テスト id=2,cd=anime,value=アニメ id=3,cd=game,value=ゲーム
PreparedStatementを利用してSQLインジェクションを防ぐ
SQLインジェクションはSQL文の構文の一部を改変することによって開発者が意図しないSQL文を不正実行してしまう攻撃手法のことです。
例えば次のような標準入力から値を取得し、cdの値で検索ができる仕組みを実装したとしましょうか。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass);
Statement statement = connection.createStatement();
Scanner sc = new Scanner(System.in)){
//検索したい文字列を入力
System.out.println("cdカラムで検索したい値を入力してね!");
String findTx = sc.nextLine();
//select文
StringBuilder sql = new StringBuilder();
sql.append("select * from test where cd='"+findTx+"';");
//SQL実行
try(ResultSet result = statement.executeQuery(sql.toString())){
while(result.next()) {
int id = result.getInt("id");
String cd = result.getString("cd");
String value = result.getString("value");
System.out.printf("id=%d,",id);
System.out.printf("cd=%s,",cd);
System.out.printf("value=%s%n",value);
}
}
}
}
}
cdカラムで検索したい値を入力してね! game id=3,cd=game,value=ゲーム
普通の検索プログラムに見えるかもしれませんが、このプログラムはSQLインジェクションの脆弱性があります。
検索値に次のような文字列を指定してみましょう。
';delete from test where true or cd='
cdカラムで検索したい値を入力してね! ';delete from test where true or cd=' Exception in thread "main" org.postgresql.util.PSQLException: クエリの実行により、複数のResultSetが返されました。 at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:290) at Test.main(Test.java:32)
エラーが発生しました。
テーブルがどうなっているか見てみましょうか。
データがありません・・・
delete文が実行され、testテーブルのデータが削除されてしまったようです。
これがSQLインジェクション攻撃の一例です。
このようなパターンの脆弱性を回避するためにJDBCにはあらかじめ、SQLインジェクションの対策を行うPreparedStatementがありますので是非使用方法を覚えるようにしましょう。
PreparedStatement statement = connection.prepareStatement(SQL文)
でインスタンスを取得できます。
引数に指定するSQL文字列はエスケープしたい箇所を?にしておきます。
statement.setString(?の順番「1~」, ?に置き換えられる値);
などで指定できます。
setStringの部分はsetIntなどもありますので、必要であれば、それぞれ引数を変更してみましょう。
それでは試しにPreparedStatementを使った検索プログラムに変更し、試してみましょうか。
テーブルが消えている場合は先に下記のSQLを流しておきましょう。
insert into test values (1,'test','テスト') ,(2,'anime','アニメ') ,(3,'game','ゲーム');
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass);
Scanner sc = new Scanner(System.in)){
//検索したい文字列を入力
System.out.println("cdカラムで検索したい値を入力してね!");
String findTx = sc.nextLine();
//select文
StringBuilder sql = new StringBuilder();
sql.append("select * from test where cd=?;");
try(PreparedStatement statement = connection.prepareStatement(sql.toString())){
statement.setString(1, findTx);
//SQL実行
try(ResultSet result = statement.executeQuery()){
while(result.next()) {
int id = result.getInt("id");
String cd = result.getString("cd");
String value = result.getString("value");
System.out.printf("id=%d,",id);
System.out.printf("cd=%s,",cd);
System.out.printf("value=%s%n",value);
}
}
}
}
}
}
cdカラムで検索したい値を入力してね! ';delete from test where true or cd='
これでSQLインジェクションは発生せず、検索処理も問題なく動きます。
トランザクション
トランザクションとは、一連の処理を一つのまとまった処理として扱ったものです。
DBを扱うプログラムでは一連の処理が正常に終了したらDBの更新内容を確定させ、異常が発生した場合は元に戻すということを行います。
これらの制御を行うにはconnection.setAutoCommit(false)を指定し、手動でcommit(確定)、rollback(キャンセル)メソッドを実行することで実現します。
次のサンプルはcd=animeのデータ行のvalueを更新するプログラムです。更新後に正常終了させるか異常発生させるかを選択させ、正常終了ならcommit、異常発生したらrollbackします。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
Connection connection = DriverManager.getConnection(url,user,pass);
try(Scanner sc = new Scanner(System.in)){
//自動コミットの無効化
connection.setAutoCommit(false);
//更新したい値を入力
System.out.println("cd=animeのvalueを何に更新する?");
String findTx = sc.nextLine();
//update文
StringBuilder sql = new StringBuilder();
sql.append("update test set value=? where cd='anime';");
try(PreparedStatement statement = connection.prepareStatement(sql.toString())){
statement.setString(1, findTx);
//SQL実行
statement.executeUpdate();
}
System.out.println("更新実行!次の操作を選択してね。");
System.out.println("0:正常終了 1:異常発生");
String ctrl = sc.nextLine();
switch(ctrl) {
case "0":break;
default:throw new SQLException();
}
//更新内容の確定
connection.commit();
} catch ( SQLException e ) {
//更新内容のキャンセル
connection.rollback();
throw e;
} finally {
//切断
connection.close();
}
}
}
まずは異常発生の動作を見てみましょう。
cd=animeのvalueを何に更新する? falskjdf 更新実行!次の操作を選択してね。 0:正常終了 1:異常発生 1 Exception in thread "main" java.sql.SQLException at Test.main(Test.java:45)
テーブルの内容が更新されていませんので想定通りの動作です。
では正常終了の動作も見ていきましょう。
cd=animeのvalueを何に更新する? fjlkasdf 更新実行!次の操作を選択してね。 0:正常終了 1:異常発生 0
想定通り、更新されました。
このようにエラー時は更新処理をなかったことにできるので非常に便利です。
CallableStatementによるストアドプロシージャの実行
PostgreSQLのストアドプロシージャを実行する方法を見ていきます。
ストアドプロシージャとはデータベースに定義できる関数です。
Javaでは、このストアドプロシージャを呼び出す方法があります。
connection.prepareCall(sql)で実行できます。
それでは実際にストアドプロシージャを定義してJavaから実行してみましょう。
CREATE OR REPLACE PROCEDURE public.proc(
IN val text, OUT res text)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
res = val || val;
END;
$BODY$;
この関数は第一引数valを二回繰り返した文字列を第二引数resに代入するだけの関数となります。
次はJavaの呼び出しコードです。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws SQLException {
//接続先の情報
String url="jdbc:postgresql://localhost:5432/";
String user="postgres";
String pass="postgres";
//接続処理
try(Connection connection = DriverManager.getConnection(url,user,pass);
Scanner sc = new Scanner(System.in)){
//call文作成
StringBuilder sql = new StringBuilder();
sql.append("call proc(?, ?)");
try(CallableStatement statement = connection.prepareCall(sql.toString())){
//第一引数INパラメータ
statement.setString(1, "3分間待ってやる。");
//第二引数OUTパラメータ設定
statement.registerOutParameter(2, Types.VARCHAR);
//実行
statement.execute();
//第二引数の値を取得
String val = statement.getString(2);
//表示
System.out.printf("%s",val);
}
}
}
}
3分間待ってやる。3分間待ってやる。
うまくストアドプロシージャを呼び出せました。










ディスカッション
コメント一覧
まだ、コメントがありません