【JavaとSQLをつなぐJDBCについて】

# はじめに
JavaとSQLをつなぐJDBC(Java Database Connectivity)の実装が難しかったので備忘録として残します。

##開発環境
・OS　　
　Windows10
・エディタ
　Ecripse
・Java	
　OpenJDK 8
・SQL
　MySQL

##1,JDBCとは
JDBC(Java Database Connectivity) はJavaとRDを接続しJavaから操作するためのAPIです。
JDBCという概念を利用してJavaからSQLへデータを登録、更新、削除したり、SQLからデータを取り出したり出来ます。
DBにはOracle、MySQL、SQLServer等様々な種類がありそれぞれDBへ接続する方法も、DBの種類によって
変わってきますが、JDBC経由でDBに接続するとDB毎の個性をほとんど気にせずアクセスできるようになります。

JDBCの歴史等わかりやすい記事があったので載せておきます。
https://data.wingarc.com/what-is-api-16084

##2,実装
実装にあたり下記手順でJDBCを実装していきます。
①JDBCのドライバのロード
②SQLとの接続の確立
③SQL文の送信
④実行結果の取得
⑤接続の解除

###2-①
①JDBCのドライバのロード

Class.forNameメソッドを使ってドライバをロード (Classオブジェクトをメモリ上に生成 )します。
・Javaのバイトコードファイル（拡張子がclassファイル）をロードするためのメソッド。
・引数としてクラスを表す文字列（パッケージ名.クラス名）を指定します。
・ロードに失敗するとClassNotFoundExceptionが発生するため、
　使用の際はtry～catchする必要があります。
後々変数として使えるように最初にフィールドで変数を宣言しておきます。

```MessageDao.java
public class MessageDao {
  //接続に必要な変数を宣言
  private static final String DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
  private static final String JDBC_URL  = "jdbc:oracle:thin:@localhost:1521:ORCL";
  private static final String USER_ID   = "imuser";
  private static final String USER_PASS = "impass";

  MessageDao (){
        // JDBCドライバのロード
        try {
            Class.forName(DRIVER_NAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
```

この状態で動かしてしまうとjava.lang.ClassNotFonudExceptionエラーがおきます。
JavaアプリケーションからDBアクセスをするには、
まずベンダーから提供される RDBMS専用のJDBCドライバを入手しなければいけません。
また、入手したJDBCドライバをJavaプログラムから使用できるようにするための設定が必要です。
JDBCドライバはOracle社のサイトからダウンロードできます。
https://www.oracle.com/technetwork/database/features/jdbc/jdbc-drivers-12c-download-1958347.htm

ダウンロード完了後、「ojdbc8.jar」というファイルがあるのでそれがJDBCドライバです。
「ojdbc8.jar」をC：あたりに配置します。

JDBCドライバをJavaプログラムから使用できるようにするには、ビルド・パスへの追加が必要なので、以下の手順で実施します。
１．Eclipseのプロジェクト上で右クリック。
２．[プロパティ] → [Java のビルド・パス] → [ライブラリー]
３．[外部Jar の追加]を押下し、C:のojdbc8.jar ファイルを選択する。
４．[適用して閉じる]を押下する。

実行を押して例外が発生しなければJDBCドライバのロードに成功しています。

###2-②
②SQLとの接続の確立

DriverManagerクラスのメソッドgetConnection()を作成して、データベースへの接続を確立します。データベースを指すように、接続URL、DBユーザー名およびDBパスワードを更新します。

```MessageDao.java
public class MessageDao {
  //接続に必要な変数を宣言
  private static final String DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
  private static final String JDBC_URL  = "jdbc:oracle:thin:@localhost:1521:ORCL";
  private static final String USER_ID   = "imuser";
  private static final String USER_PASS = "impass";

  MessageDao (){
        // JDBCドライバのロード
        try {
            Class.forName(DRIVER_NAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

　 public ArrayList<MessageDto> select() {
　　ArrayList<MessageDto> list = new ArrayList<>();

    // コネクションクラスの宣言
    Connection con = null;
    // ステートメントクラスの宣言
    PreparedStatement ps = null;
    // リザルトセットクラスの宣言
    ResultSet rs = null;

    // データベースにアクセス
    try {
      // データベースとの接続の確立を行う。
      con = DriverManager.getConnection(JDBC_URL, USER_ID, USER_PASS);

　  }
  }
}
  
```
DriverManagerクラスとは、
・JDBCドライバマネージャのクラスです。JDBCドライバにアクセスしてRDBMSを操作するための様々な機能が提供されていて、
getConnectionメソッドでRDBMSに接続し、接続に成功した場合は、DB接続に関する情報を有するConnectionオブジェクトを戻り値として返します。
　引数にDBへの接続情報（接続先DB、ユーザーID、パスワード）を指定することで接続が実行されます。

後でSQLから抽出した結果を格納するために使用する箱を、
ArrayList<MessageDto> list = new ArrayList<>();
でMessageDto型のArrayListをlistとして空の箱を生成しておきます。

データを定義するDTOが必要なのでMessageDto.javaクラスを作成してデータを定義出来るようにしておきます。
###2-③
③SQL文の送信
Statement系オブジェクトのexecuteQueryメソッドやexecuteUpdateメソッドでSQL文を実行するようRDBに依頼します。

```MessageDao.java
public class MessageDao {
  //接続に必要な変数を宣言
  private static final String DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
  private static final String JDBC_URL  = "jdbc:oracle:thin:@localhost:1521:ORCL";
  private static final String USER_ID   = "imuser";
  private static final String USER_PASS = "impass";

  MessageDao (){
        // JDBCドライバのロード
        try {
            Class.forName(DRIVER_NAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

　 public int insert(MessageDto dto) {

    // コネクションクラスの宣言
    Connection con = null;
    // ステートメントクラスの宣言
    PreparedStatement ps = null;
    // 処理結果（件数）用変数
    int result = 0;
    // データベースにアクセス
    try {
      // データベースとの接続の確立を行う。
      con = DriverManager.getConnection(JDBC_URL, USER_ID, USER_PASS);

//SQL文の生成（SELECT文）
   StringBuilder builder = new StringBuilder();
      //SQL文のSELECT *で全件取得
      builder.append("SELECT *");
      builder.append("FROM ");
      builder.append("  MESSAGE_BOARD ");
      builder.append("ORDER BY ");
      builder.append("  ID DESC ");

      // ステートメントクラスにSQL文を格納
      ps = con.prepareStatement(builder.toString());

　  }
  }
}
  
```

StringBuilder型のbuilderを変数として宣言してるのはappendメソッドで文字列同士の結合を行うためです。文字列同士の結合は"+演算子"でも可能ですが、StringBuilderのappendメソッドを使用した方がプログラムの処理スピードが早くなるためStringBuilder型の変数を宣言してappendメソッドを使用して結合しています。

###2-④
④実行結果の取得

ResultSetオブジェクトを使用して送信したSELECT文の抽出結果のデータを取得します。

```MessageDao.java
public class MessageDao {
  //接続に必要な変数を宣言
  private static final String DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
  private static final String JDBC_URL  = "jdbc:oracle:thin:@localhost:1521:ORCL";
  private static final String USER_ID   = "imuser";
  private static final String USER_PASS = "impass";

  MessageDao (){
        // JDBCドライバのロード
        try {
            Class.forName(DRIVER_NAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

　 public int insert(MessageDto dto) {

    // コネクションクラスの宣言
    Connection con = null;
    // ステートメントクラスの宣言
    PreparedStatement ps = null;
    // 処理結果（件数）用変数
    int result = 0;
    // データベースにアクセス
    try {
      // データベースとの接続の確立を行う。
      con = DriverManager.getConnection(JDBC_URL, USER_ID, USER_PASS);

//SQL文の生成（SELECT文）
   StringBuilder builder = new StringBuilder();
      //SQL文のSELECT *で全件取得
      builder.append("SELECT *");
      builder.append("FROM ");
      builder.append("  MESSAGE_BOARD ");
      builder.append("ORDER BY ");
      builder.append("  ID DESC ");

      // ステートメントクラスにSQL文を格納
      ps = con.prepareStatement(builder.toString());

      // SQLを実行して取得結果をリザルトセット（rs）に格納
      rs = ps.executeQuery();

　　　while (rs.next()) {
       // 取得結果を格納するDtoをインスタンス化
       MessageDto dto = new MessageDto();
       // dtoに取得結果を格納
       dto.setId       (rs.getInt("id"));
       dto.setName     (rs.getString("name"));
       dto.setMessage  (rs.getString("message"));
       dto.setCreatedAt(rs.getTimestamp("created_at"));
       // Dtoに格納された1レコード分のデータをリストに詰める
       list.add(dto);
     }


　  }
  }
}
  
```
ResultSetオブジェクトはStatement系クラスが持つexecuteQueryメソッドの戻り値として受け取る
ことができるオブジェクトで、送信したSELECT文の抽出結果のデータがrsに格納されています。
SQLから受け取った実行結果をループで回して１レコードずつ取得することができます。
セット完了後先ほど生成した空のlistにセットした値をaddします。


###2-⑤
⑤接続の解除

データベースへのアクセスが終了したら明示的に接続を解除する必要があるため、Connectionオブジェクト、Statement系オブジェクト、ResultSetオブジェクト、それぞれでcloseメソッドを使用して接続を解除（クローズ）します。

※接続をクローズしないと接続された状態が続くことになり、開発中は問題が顕在化されないですが運用を開始して思わぬアクセス数やデータ量が多くなってくると負荷がかかり初めて問題が発生するという事態に繋がる可能性があります。 必ずクローズする処理を書くようにします 。

```MessageDao.java

public ArrayList<MessageDto> select() {

    ArrayList<MessageDto> list = new ArrayList<>();

    // コネクションクラスの宣言
    Connection con = null;
    // ステートメントクラスの宣言
    PreparedStatement ps = null;
    // リザルトセットクラスの宣言
    ResultSet rs = null;

    // データベースにアクセス
    try {
      // データベースとの接続を行う
      con = DriverManager.getConnection(JDBC_URL, USER_ID, USER_PASS);

      StringBuilder builder = new StringBuilder();
      builder.append("SELECT ");
      builder.append("   id ");
      builder.append("  ,name ");
      builder.append("  ,message ");
      builder.append("  ,created_at ");
      builder.append("FROM ");
      builder.append("  message_board ");
      builder.append("ORDER BY ");
      builder.append("  ID DESC ");
      // ステートメントクラスにSQL文を格納
      ps = con.prepareStatement(builder.toString());
      // SQLを実行して取得結果をリザルトセットに格納
      rs = ps.executeQuery();
      // リザルトセットから1レコードずつデータを取り出す
      while (rs.next()) {
        // 取得結果を格納するDtoをインスタンス化
        MessageDto dto = new MessageDto();
        // Dtoに取得結果を格納
        dto.setId       (rs.getInt("id"));
        dto.setName     (rs.getString("name"));
        dto.setMessage  (rs.getString("message"));
        dto.setCreatedAt(rs.getTimestamp("created_at"));
        // Dtoに格納された1レコード分のデータをリストに詰める
        list.add(dto);
      }

    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        if (rs != null) {
          rs.close();
        }
        if (ps != null) {
          ps.close();
        }
        if (con != null) {
          con.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    // 呼び出し元に取得結果を返却
    return list;
  }
  
```

以上でJDBCの実装が完了です。
SQLの追加、更新、削除は接続の書き方が少し変わるのでそのあたりと、JDBCテンプレートについてもまとめていこうと思います。
今後JDBCの実装をする方に少しでも参考になれば幸いです。

##参考文献
・https://docs.oracle.com/cd/E96517_01/tdpjd/creating-java-bean-implementation-jdbc-connection.html
・https://data.wingarc.com/what-is-api-16084