Java JDBC

JavaGoldの資格取得のためにアウトプットかつ、備忘録のための記事です。
誤り等、わかりずらい等があればご指摘お願いいたします:bow_tone3:

# JDBCを使用したデータベース接続
内容
・JDBCを使用したデータベース接続
・SQLステートメントの実行
・ResultSetの拡張
## JDBCとは
JDBCとは、Javaプログラムからデータベースにアクセスするための共通インタフェース。
使用するパッケージは以下
・`java.sql`：データベースに格納されたデータへアクセスして処理する基本APIを提供。
・`javax.sql`：データベースにアクセスする際、サーバー側での処理（接続プールや分散トランザクション等）を行う場合に使用するAPI

データベースにアクセスするクラスは、共通インタフェースとして提供されているJDBC APIを使用することで、接続するデータベース製品の違いを意識することなく実装できる。

## JDBCドライバ
JDBCドライバはJavaプログラムとデータベースを紐づけるために必要なJDBCインタフェースを実装したクラス。

## 基本的なJDBCアプリケーションの作成
JDBCを使用するときの一般的な流れ
```text:流れ
１：java.sqlパッケージのインポート
　　　　　　↓
２：データベースの指定
　　　　　　↓
３：データベースとの接続
　　　　　　↓
４：ステートメントの取得
　　　　　　↓
５：SQL文の実行
　　　　　　↓
６：結果の取得と処理
　　　　　　↓
７：接続のクローズ
```
また、java.sqlパッケージで提供されている主なクラス、インタフェースは以下↓
・`DriverManagerクラス`：データベースのドライバを管理し、データベースとの接続を支援する。
・`Connectionインタフェース`：特定のデータベースとの接続（セクショ）を表現する。
・`Statementインタフェース`：静的SQL文を実行し、作成された結果を返すために使用さるオブジェクト。
・`ResultSetインタフェース`：データベースの結果セットを表すデータオブジェクト。

```java:JDBCの使用例
		Connection con = null;
		Statement stmt = null;
		ResultSet rst = null;
		
		try {
			// ２：データベース指定
            // 指摘修正
			//String url = "jdbc:mysql//○○";
            String url = "jdbc:mysql://<hosts>/<database>"; 
			
			// ３：データベースとの接続
			con = DriverManager.getConnection(url, "root", "training");
			
			// ４：ステートメントの取得
			stmt = con.createStatement();
			
			// ５：SQL文の実行
			String sql = "select * from department";
			rst = stmt.executeQuery(sql);
			
			// ６：結果の取得と処理
			while(rst.next()) {
				System.out.println("dept_code : " + rst.getInt(1));
				System.out.println("dept_name : " + rst.getString(2));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// ７：接続のクローズ
			try {
				if (rst != null) rst.close();
				if(stmt != null) stmt.close();
				if (con != null) con.close(); 
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
```

解説
`２：データベース指定`
通信先のデータベースを特定するためのJDBC URLをも実で準備している。
URLの構文は以下の通り。
プロトコル、サブプロトコル、サブネーム名を含み、:で区切る
```text:JDBC URLの構文
jdbc:<subprotocol>:<subname>
```

`３：データベースとの接続`
データベース接続をするには`DriverManagerクラス`の`getConnection()メソッド`を呼び出し、Connectionオブジェクトを取得する。
以下がDriverManagerクラスの主な接続用メソッド
`static Connection getConnection(String url) throws SQLException`
　┗指定されたデータベースのURLへの接続を試みる

`static Connection getConnection(String url, String user, String password) throws SQLException`
 　┗指定されたデータベースのURLへ、ユーザー名・パスワードを使用して接続を試みる

ConnectionObjectを取得できなかった場合は、SQLException例外がスローされる

`４：ステートメントの取得`
標準的な問い合わせを実行するには、Statementオブジェクトを使用する。
StatementオブジェクトによりSQLコマンドを実行する。Statemntオブジェクトは、Connectionインタフェースの`createStatement()メソッド'で取得する。

`５：SQL文の実行`
SQL文を文字列でまず用意する。
Statementインタフェースのメソッドを使用してSQL文を実行する。
今回はSELECT文なので、`executeQuery`を使用する。

`６：結果の取得と処理`
executeQueryの実行によって、ResultSetオブジェクトとして検索結果が返される。
ResultSetオブジェクトは、問い合わせにより返されたデータを表し、1行ずつ処理ができる。
ResultSetオブジェクトから値を取り出すには以下のように列・行にアクセスする。
・行へのアクセス
`next()`メソッドにより、1行ずつデータを取り込む。
`next()メソッド`は、1行ずつデータにアクセスし、次の行があればtrueを返す
ResultSetオブジェクト生成時は、カーソルが先頭行の前にあるため、必ず一回はnext()メソッドを実行しないと行を取り出すことはできない。
next()を一回も呼び出さずにデータの取り出しを試みると**SQLException**が発生する

・列へのアクセス
*getter*メソッドで引数に列番号を指定することによりデータを取り出せる。
列番号はResultSetオブジェクト内の左から右へ順に降られた番号で、最初の列は1。
また、列名を指定して取り出すこともできる。
getterメソッドでは、**getXXX(列名)** もしくは**getXXX(列番号)** というメソッド名が定義されており、XXXの部分にはJavaのデータ型名が入る。

`７：接続のクローズ`
すべての処理が終了したらリソースの解放をす。
Connectionインタフェース、Statementインタフェース、ResultSetインタフェースは使用したリソースを解放するために`close()メソッド`を提供している。

### 例外
Javaアプリケーションがデータベースと通信した時に発生するエラーはデータベースからアプリケーションへ通知される。エラーの通知には`SQLException`オブジェクトが使用される。
発生する理由は様々。
例↓
・ネットワークケーブルが物理的な問題により通信が切断した。
・SQLコマンドのフォーマットが不適切である。
・サポートされていない機能を使用。
・存在しない列を参照した。

SQLExceptionクラスのメソッドを使用して、エラーメッセージを取得することができる。
主なクラス↓
`int getErrorCode()`
ベンダ固有の例外コードを取得する。

`String getSQLState()`
SQLStateを取得する。

## SQLステートメントの実行
JDBCでは3種類のステートメントを使用できる。
インタフェースとして提供される。
`Statement`：標準的なSQL文を実行
`PreparedStatement`：プリコンパイルされたSQL文を実行
`CallableStatement`：ストアドプロシージャを実行

### Statementインタフェース
標準的な問い合わせを実行するには、ConnectionインタフェースのcreateStatementメソッドからStatementオブジェクトを取得する。

Statementインタフェースの主なメソッド
`ResultSet executeQuery(String sql) throws SQLException`
単一のResultSetオブジェクトを返す指定されたSQL文を実行する。
該当するレコードがない場合でもnullにはならない。

`int executeUpdate(String sql) throws SQLException`
指定されたSQL文を実行する。SQL文はデータ操作言語文(INSERT,UPDATE,DELETE)、あるいは、DDL文のような何も返さないSQL文を指定する。
戻り値は、引数がDML文の場合は、行数を返す。何も返さないSQL文の場合は0を返す。

`boolean execute(String sql) throws SQLException`
SQL文の実行結果がResultSetオブジェクトの場合はtrueを、更新行数または結果がない場合はfalseを返す

#### SELECT - executeQuery()メソッドで問い合わせ
```java:executeQueryメソッドの使用例
		String sql = "select * from department where dept_code = " + args[0];
		
		try(Connection con = DBConection.connection();
				Statement st = con.createStatement();
				ResultSet rst = st.executeQuery(sql)) {
			
			if(rst != null) System.out.println("rs != null");
			while(rst.next()) {
				System.out.println("dept_name : " + rst.getString("dept_name"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
```

#### INSERT - executeUpdateメソッドで問い合わせ
```java:executeUpdateメソッドによるINSERT処理
		String sql = "INSERT INTO department values " + "(6,'planning', 'Yokohama', '000-0000-0000')";
		try(Connection con = DBConection.connection();
				Statement st = con.createStatement();) {

            // 指摘修正
			// int col = st.executeUpdate(sql);
            int row = st.executeUpdate(sql);
			
			System.out.println("col : " + col);
		} catch (Exception e) {
			e.printStackTrace();
		}
```
#### UPDATE - executeUpdateメソッドで問い合わせ
```java:executeUpdateによるUPDATE処理
		String sql = "update department set dept_address = 'Tokyo' where dpt_code = " + args[0];
		
		try(Connection con = DBConection.connection();
				Statement st = con.createStatement();) {
			
			int col = st.executeUpdate(sql);
			
			System.out.println("col : " + col);
		} catch (Exception e) {
			e.printStackTrace();
		}
```
更新できた件数を返す。

#### DELETE - executeUpdate()メソッドで問い合わせ
```java:executeUpdateメソッドによるDELETE処理
		String sql = "delete from department where dpt_code = " + args[0];
		
		try(Connection con = DBConection.connection();
				Statement st = con.createStatement();) {
			
			int col = st.executeUpdate(sql);
			
			System.out.println("col : " + col);
		} catch (Exception e) {
			e.printStackTrace();
		}
```

#### execute()メソッドでSQL文の実行
一般的には検索処理文の実行にはexecuteQuery(),更新処理文の実行にはexecuteUpdate文を使用するが、どっちの処理でも実行可能なメソッドとして**execute()** が提供されている。
引数には、検索/更新いずれの処理文が指定可能。
処理内容によって戻り値が違うので、戻り値の型はboolean型になっている。
ResutlSetオブジェクトが返ってきている場合は、true
更新行もしくは結果がない場合は、falseが返却される。
以下は結果を取り出すStatementインタフェースのメソッド↓
`ResultSet getResutlSet() throw SQLException`
┗ ResultSetオブジェクトを取得する

`int getUpdateCount throws SQLException`
┗ 更新行数を取得する。

```java:execute()メソッドの利用
		String[] sqls = {
				"insert into department values (7, 'planing', 'Yokohama', '000-0000-0000')",
				"select dept_name from department where dept_code = 2"
		};
		
		try(Connection con = DBConection.connection();
				Statement st = con.createStatement();) {
			
			for(String sql : sqls) {
				boolean isResultSet = st.execute(sql);
				if (isResultSet) {
					ResultSet rs = st.getResultSet();
					rs.next();
					System.out.println(rs.getString(1));
				}else {
					int count = st.getUpdateCount();
					System.out.println(count);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
```

#### executeXXXメソッドの特徴
以下の通り↓
`execute(String sql)`
戻り値：boolean
ture ：ResultSetオブジェクトの取得が可能
false：更新行の取得が可能 or 結果がない

`executeQuery(String sql)`
戻り値：ResultSet
結果の取得には一回はnext()を呼ばないといけない。
該当コードがない場合でも、ResultSetはnullにはならない

`executeUpdate(String sql)`
戻り値：int
更新行が返る。
更新した行がなかった場合は0が返る。


## ResultSetの拡張
### ResultSetオブジェクトの高度な機能
ResultSetオブジェクトでは以下のような機能を使用できる。
・問い合わせ結果のスクロール、絶対/相対位置特定
・ResultSetオブジェクト上でデータの挿入・更新

スクロール機能や更新処理が可能なResultSetオブジェクトを取得するには、各ステートメントの取得時に、ResultSetインタフェースで提供されている定数を指定する。

以下がResultSetインタフェースで定義されている定数
`CONCUR_READ_ONLY`：更新できないResultSetオブジェクトの並行処理モードを示す
`CONCUR_UPDATABLE`：更新できるResultSetオブジェクトの並行処理モードを示す
`TYPE_FORWARD_ONLY`：カーソルが順方向にしか移動しないResultSetオブジェクトを示す
`TYPE_SCROLL_INSENSITIVE`：スクロール可能だが、データベースのデータに対して行われた変更を反映しないResultSetオブジェクトのタイプを示す
`TYPE_SCROLL_SENSITVE`：スクロール可能でデータベースの最新の内容を反映するResultSetオブジェクトのタイプを示す

### 問い合わせ結果のスクロール、絶対/相対位置指定
Statementオブジェクトを使用して、スクロール可能なResultSetオブジェクトを取得する
次のコード例はStatementオブジェクトを取得する際に、ResultSetインタフェースの定数を指定している
```java:コード例
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
```
createStatement()メソッドの第一引数に、TYPE_SCROLL_INSENSITIVEもしくはTYPE_SCROLL_SENSITVEを指定することで、ResultSetオブジェクト内を順方向or逆方向に移動できる。
第二引数には、CONCUR_UPDATABLEを指定することで取得したResultSetオブジェクトを通じてレコードの挿入や更新、削除を行うことができる。
スクロール可能なResultSetオブジェクトでは、next()メソッド以外にもカーソルを任意に移動するメソッドを使用できる。
以下、ResultSetインタフェースのカーソル移動用メソッド↓
`boolean absolute(int row) throws SQLException`
 ┗ カーソルをこのResultSetオブジェクト内の指定された行番号に移動する。

`boolean relative(int row) throws SQLException`
 ┗ カーソルを正または、負の相対行数だけ移動する

`boolean next() throws SQLException`
 ┗ カーソルを現在位置から1行順方向に移動する

`boolean previous() throws SQLException`
 ┗カーソルをこのResultSetオブジェクト内の前の行に移動する

`boolean first() throws SQLException`
 ┗ カーソルをこのResultSetオブジェクト内の先頭の行に移動する

`boolean last() throws SQLException`
 ┗ カーソルをこのResultSetオブジェクト内の最終行に移動する

`void afterLast() throws SQLException`
 ┗ カーソルをこのResultSetオブジェクト内の最終行の直前に移動する

`void beforeFirst() throws SQLException`
 ┗ カーソルをこのResultSetオブジェクト内の先頭行の直前に移動する

`int getRow() throws SQLException`
 ┗ 現在の行番号を取得する。最初の一行は1となる


# 指摘修正
１：2024-04-18