###■ MVC の Model ( JDBC )
Webアプリケーションの制御の役割: Contoller(サーブレット)
Webアプリケーションの画面の役割: View( JSP )
Model は、アプリケーションのメインの処理を担う部分
役割としては、受けたリクエストに対してコントローラーから指示を受け、データベース接続やデータの操作、加工を行う
このデータの処理をおこなうにあたって、JDBC ( Java DataBase Connectivity ) というJava からデータベースに簡単に接続できる技術(手順)を利用する
決められた手順に従い Java のプログラム内で順番に呼び出していくことでデータベースの制御ができる
JDBC は MySQL や Postgre 、 SQLserver など、俗に RDB と呼ばれるものに対してはどれもドライバが用意されており、どのドライバも手順が共通化されている(例えばMySQL であれば公式サイトからドライバがダウンロードできる)
※Eclipse にデフォルトで入っている Maven というツールでも DB との接続は可能
###■ JDBC による Java とデータベース接続の手順
JDBC を利用した DB 操作は最大6つの手順で構成されている
1.JDBC ドライバのクラスを Java 上で読み込む
2.データベースの接続を実行
3.データベースとやり取りする窓口(ステートメントオブジェクト)の作成
4.SQL を実行する
5.結果の取得・表示( SELECT 文の場合のみ)
6.データベースとの接続を終了
####・以下、INSERT 文の場合の例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Jdbc1 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
try {
// ①JDBC ドライバのクラスを Java 上で読み込む
Class.forName("com.mysql.cj.jdbc.Driver");
// ②データベースへの接続を実行
String url = "jdbc:mysql://localhost/database1";
String user = "root";
String password = "12345678";
con = DriverManager.getConnection(url, user, password);
// ③データベースとやり取りする窓口(ステートメントオブジェクト)の作成
String sql = "INSERT INTO list(name,age)VALUE('Tanaka',20)";
ps = con.prepareStatement(sql);
// ④SQL の実行
int count = ps.executeUpdate();
System.out.println(count + "件処理しました");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// ⑥データベースとの接続を終了
try {
if (ps != null) {
ps.close();
}
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
####Connection 型 と PreparedStatement 型
Connection con = null;
PreparedStatement ps = null;
このConnection 型 と PreparedStatement 型 の2行はデータベースとの接続の状態を確保するための変数
Connection はデータベース接続の役割、PrepareStatement は SQL 文を解析する役割を担う
⑥でDBとの接続を閉じるときに Connection オブジェクトのメソッドを使用するため、以下のtry ブロック内ではなく(閉じられなくなるため)、メソッド内全体で変数の宣言を行う
####① JDBC ドライバのクラスを Java 上で読み込む
Class.forName("com.mysql.cj.jdbc.Driver");
Class クラスの forName メソッドは、プログラムで必要となる JDBC ドライバのロードを行う 引数には利用するデータベースの JDBC ドライバを指定する
このメソッドを利用すると例外が投げられる可能性があるため、例外処理(ClassNotFoundException)が必要(⇒"囲む"で挿入する)
####② データベースへの接続の実行
String url = "jdbc:mysql://localhost/database1";
String user = "root";
String password = "12345678";
con = DriverManager.getConnection(url, user, password);
ここではデータベースへ接続するための情報を指定
######"jdbc:mysql://localhost/database1";
JDBC を使って MySQL に接続しますよ、接続先は localhost で、データベース名は database1 ですよ
接続するユーザーの名前は "root" で、パスワードは "12345678" ですよ、という内容
最終行で実際にデータベースへの接続処理を行っている
######con = DriverManager.getConnection(url, user, password);
DriverManager クラスの getConnection メソッドに、上記で指定した情報を渡すことで接続が実行される
またメソッドの戻り値の型である Connection (インターフェース)java.sql パッケージに宣言されているインターフェースのためインポートが必要
且つ SQLException が投げられる可能性があるため、例外処理を追加する
####③ データベースとやり取りする窓口(ステートメントオブジェクト)の作成
String sql = "INSERT INTO list(name,age)VALUE('Tanaka',20)";
ps = con.prepareStatement(sql);
INSERT 文で list テーブルに名前、年齢を登録(今回使用していテーブルでは、 id を AUTO_INCREMENT で1から自動作成するようにしている)
下の prepareStatement メソッドで引数に前行で記述した SQL 文を格納した変数 sql を渡し、解析・SQL 文の確定をしている
Statement オブジェクトは DB に SQL を依頼する場合に必要となるオブジェクト
createStatement メソッドがパラメータなしのSQLを実行する際に使用するのに対して、prepareStatement メソッドは今回のようにパラメータを渡してSQLを実行する場合に使用する
次の executeUpdate メソッドを呼び出すのに利用する
####④ SQLの実行
int count = ps.executeUpdate();
System.out.println(count + "件処理しました");
ここでSQL文の実行を行う
excecuteUpdate メソッドは INSERT や UPDATE , DELETE など実行した結果として何か受け取るということがない SQL 文で使用する
また Statement オブジェクトで他に用意されている executeQuery メソッドは SELECT 文で使用する(検索)
戻り値は追加(変更した)行数になる(今回は1行)
⇒ ※参考で処理件数を表示させている
ちなみに executeQuery メソッドは、戻り値は メソッドによって作成されたデータを含む ResultSet オブジェクト
参考:https://tinyurl.com/ygk2yndy
####⑤ 結果の取得・表示( SELECT 文の場合のみ)
今回のコードではSELECT文を使用していないためこの部分はない
####⑥ データベースとの接続を終了
finally {
try {
if (ps != null) {
ps.close();
}
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
オブジェクトを使用し終わったらリソース解放するために明示的接続を閉じる処理を行う必要がある
DB との接続を閉じる場合は、取得した Connection オブジェクトの close メソッドを呼び出す
順番は ResultSet がある場合は ResultSet ⇒ PreparedStatement ⇒ Connection の順に閉じる
また切り忘れ、切り漏れ(1つだけ閉じてないとか)がないように例外発生時でも閉じるようにする
####・以下、SELECT 文の場合の例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Jdbc2 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// ①JDBC ドライバのクラスを Java 上で読み込む
Class.forName("com.mysql.cj.jdbc.Driver");
// ②データベースへの接続を実行
String url = "jdbc:mysql://localhost/database1";
String user = "root";
String password = "12345678";
con = DriverManager.getConnection(url, user, password);
// ③データベースとやり取りする窓口(ステートメントオブジェクト)の作成
String sql = "SELECT * FROM list";
ps = con.prepareStatement(sql);
// ④SQL文の実行
rs = ps.executeQuery();
// ⑤結果の取得・表示( SELECT 文の場合のみ)
while (rs.next()) {
System.out.print("id:" + rs.getInt("id") + " ");
System.out.print("name:" + rs.getString("name") + " ");
System.out.println("age:" + rs.getInt("age"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// ⑥データベースとの接続を終了
try {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
INSERT 文の例と異なる点は
冒頭の変数宣言にResuleSet が加わっている、③、④と SELECT 文のため⑤が追加されている
ResultSet はSELECT 文の結果を格納する役割を担う
これも他オブジェクト同様閉じる必要があるため、close 処理を記述する
####③ データベースとやり取りする窓口(ステートメントオブジェクト)の作成
String sql = "SELECT * FROM list";
ps = con.prepareStatement(sql);
SELECT 文で list の全てのレコードを取得
パラメータを渡すため PrepareStatement を使用
####④ SQL 文の実行
rs = ps.executeQuery();
SELECT 文のため executeQuery メソッドを使用、DB から該当する処理結果を取得し、SELECT 文の実行結果は ResultSet のオブジェクトに格納される、実行して格納
####⑤ 結果の取得・表示( SELECT 文の場合のみ)
while (rs.next()) {
System.out.print("id:" + rs.getInt("id") + " ");
System.out.print("name:" + rs.getString("name") + " ");
System.out.println("age:" + rs.getInt("age"));
}
ResultSet オブジェクトは検索の結果を表のイメージで持っており、またカーソルという、データの位置を指し示す機能があり、最初は取得したレコードの1行目の1つ上を指している(idカラムが1,2...とあるとすると、その上の id というカラム名が書いてあるレコードを指しているイメージ)
これを next メソッドでカーソルを移動させていき(While で繰り返すのが一般的)、レコードが存在すれば取得するという流れ
その後、rs.getInt や getString でカラムを指定して値を表示させている
####・検索機能など動的なSQL
Yahoo、Google などの検索サイトやAmazon などのショッピングサイトではクライアントが自由に検索ワードを入力でき、それが各サービスのデータベースの検索に SQL として利用され、検索結果を取得・表示している
先述したような固定の SQL 文ではなく、動的な SQL 文を作成する
package jdbc;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Jdbc3 {
public static void main(String[] args) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// ①
Class.forName("com.mysql.cj.jdbc.Driver");
// ②
con = DriverManager.getConnection(
"jdbc:mysql://localhost/database1",
"root",
"12345678");
// ③
String sql = "SELECT * from list WHERE name = ? AND age = ?";
ps = con.prepareStatement(sql);
System.out.print("名前を入力してください > ");
String input = keyIn();
ps.setString(1, input);
System.out.print("年齢を入力してください > ");
int input2 = Integer.parseInt(keyIn());
ps.setInt(2, input2);
// ④
rs = ps.executeQuery();
// ⑤
while (rs.next()) {
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.print(name + ":");
System.out.println("Age" + age);
}
} catch (ClassNotFoundException e) {
System.err.println("JDBCドライバのロードに失敗");
e.printStackTrace();
} catch (SQLException e) {
System.err.println("データベースに以上が発生");
e.printStackTrace();
// ⑥
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
System.err.println("データベースに以上が発生");
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
System.err.println("statementを閉じるときにエラーが発生");
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
System.err.println("データベース接続時にエラーが発生");
e.printStackTrace();
}
}
}
}
//同クラス内で実行するため keyIn メソッドは static で宣言
private static String keyIn() {
String str = null;
try {
BufferedReader key = new BufferedReader(new InputStreamReader(System.in));
str = key.readLine();
} catch (IOException e) {
}
return str;
}
}
これまでと異なるのは③
String sql = "SELECT * from list WHERE name = ? AND age = ?";
ps = con.prepareStatement(sql);
System.out.print("名前を入力してください > ");
String input1 = keyIn();
ps.setString(1, input1);
System.out.print("年齢を入力してください > ");
int input2 = Integer.parseInt(keyIn());
ps.setInt(2, input2);
動的に変化させたい部分を WHERE 句の ? を利用し、後ほどここに値を設定する
preparedStatement で SQL 文を確定させ、その後 preparedStatement オブジェクトの setString、setInt メソッドで値を設定する
この時
ps.setString(1, input1);
ps.setInt(2, input2);
上記の両第1引数の数字「1、2」は、先述した SQL 文の ? の順番を指定している
? はいくらでも記述できるが、どれかわからないため順番を指定する必要がある
第2引数にはその値を指定するが、今回は動的に処理を行うため、クライアントが keyIn メソッドによって入力した値を 変数 input1 , input2 に格納して引数に指定している
keyIn メソッドはキーボードから入力した値を文字列として取得するメソッドとして、同じクラス内で main メソッドから呼び出すため、main メソッド外に static なメソッドとして定義している
以下
private static String keyIn() {
String str = null;
try {
BufferedReader key = new BufferedReader(new InputStreamReader(System.in));
str = key.readLine();
} catch (IOException e) {
}
return str;
}
}
実際にキーボード入力値を取得するには java.io パッケージの BufferedReader クラスと InputStreamReader クラスを利用する
######BufferedReader key = new BufferedReader(new InputStreamReader(System.in));
BufferedReader
まず内側から、 System.in は System クラスのフィールド
System.out.println の out も同じく System クラスのフィールドで、System クラスは java.lang パッケージの中に含まれ、最初から例外的にインポートされた状態のため、そのまま使用できる
out が出力の処理を行うのに対して、in は入力の処理を行う
System.in はバイト列の読み込みのみ行い格納する、そのため後に文字列に変換する処理を行う必要がある
次に inputStreamReader はバイト列を文字列に変換する役割を持つ
System.in で読み込み、格納したバイト列を引数で受けて変換する
大外の BudfferedReader はテキストファイルを1行ずつ読み込むためのクラス(同じくテキストファイルを読み込む FileReader クラスは1文字ずつ読み込む)
inputStreamReeader で変換した文字列を引数に受けて、 readLine メソッドで指定したテキストファイルを1行ずつ読み込み、 String 型の戻り値として返し、ファイルの終わりに着いた場合は null を返す
また readLine メソッドは IOException が投げられる可能性があるため、try ~ catch で囲む
こうしてバイト列の読み込み~文字列の取り込みまで行った後、return str で値を返し、executeQuery で実行して ResultSet という流れ
【参考】
https://docs.oracle.com/javase/jp/6/api/java/io/InputStreamReader.html
https://blog.kujira-station.com/201406081333