Javaでデータベースに繋ぐ(その1) たぶん基本のやりかた

# Javaでデータベースに繋ぐには?

Javaのコードにおいて、あまり外部ライブラリに依存せずに使うための方法として、基本としてよく出るのがJDBCというシステムを使う方法です。
これはJavaの標準として用意されているものであり、実際のデータベースとの接続に際して、コネクタと呼ばれるライブラリを併用します。

併用するのですが、接続のために必要なコード自体はJDBCの範囲で基本的に書けるため、コネクタを複数用意すれば、データベースのシステムを切り替えることも比較的容易に行えます。

ここではまず、Javaの開発キット(JDK)に標準で添付されているDerbyを使う場合でコードを書いてみましょう。
なおここでは、JDK標準添付のDerbyで行うことにします。プロジェクトにはDerbyのライブラリーを追加しておくようにしてください。参考資料として、「[ユーザー・ライブラリーでDerbyをまとめてしまおう](http://qiita.com/fugahogeds/items/7069f2f2ff2389f8a450)」を併せてご利用ください。

# 基本のコード

では基本のコードをだします。とりあえず開発環境はEclipseとしておきます。
適当なプロジェクトを作成し、クラスとして `Sample1` を使うことにします。

```java
/**
 * データベース接続のサンプル(その1: Statement方式)
 * 
 * @author Sato Daisuke
 */

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Sample1 {
    public static void main(String[] args) throws SQLException {
        // (1) 接続用のURIを用意する(必要に応じて認証指示user/passwordを付ける)
        String uri = "jdbc:derby:memory:sample;create=true";

        // (2) DriverManagerクラスのメソッドで接続する
        Connection conn = DriverManager.getConnection(uri);

        // (3) SQL送信用インスタンスの作成
        Statement st = conn.createStatement();

        // (4) SQL送信
        st.executeUpdate("create table sample(id integer primary key)");
        Long start = System.currentTimeMillis();
        Long delta = start;

        // とりあえず1万回のループ
        for (int i = 0; i < 10000; i++) {
            st.executeUpdate("insert into sample values(" + i + ")");

            // スプリットの計算
            if (i % 1000 == 0) {
                Long now = System.currentTimeMillis();
                Long split = now - delta;
                System.out.println("" + i + ": " + split + "ms.");
                delta = now;
            }
        }
        Long end = System.currentTimeMillis();
        System.out.println("所要時間: " + (end - start) + "ms.");

        // (5) 後始末(インスタンスの正常クローズ)
        st.close();
        conn.close();
    }
}
```

ちゃちゃっと解説しましょう。

## 接続文字列の準備

JDBCでは、特定のDBに依存しにくくなっているものの、どのデータベースのシステムに繋ぐかがわからないと繋ぎようがありません。
そこで、接続に対する文字列(ここではこれを「接続文字列」と呼ぶことにします)を用意し、その中でコネクタに対応するシンボルで接続方法を取得する構造になっています。

```java
        // (1) 接続用のURIを用意する(必要に応じて認証指示user/passwordを付ける)
        String uri = "jdbc:derby:memory:sample;create=true";
```

1. `jdbc:` の部分はJDBCであることを示す文字列のため固定です
2. `derby` の部分がコネクタの名前になります、コネクタがJDBCに認識された時にそれぞれが持つ「名前」が登録されるため、それと比較して接続するコネクタを選定することになります
3. 以降の部分(上記コードであれば `memory`以降)はコネクタに依存するコードになっています

Derbyの場合、組み込み用途を意識したライブラリ型であれば今回の形で書けます。
その一方でクライアント・サーバー型(C/S)の場合はURLよろしく書くことになります。
たとえば `jdbc:derby://hogeserver/fuga` といった具合になります。
どちらにおいても、メモリデータベースというものが利用可能であり、`memory`キーワードを入れることでストレージ上でなくメモリ上に一時DBを作成します。この場合DBを持つシステムが終了した場合(組み込み型であればプログラムの終了、C/Sであればサーバーの終了など)に消滅します。

MySQLの場合、基本的にC/Sですから、 `jdbc:mysql://hogeserver/fuga`形式になります。

## 接続状態の取得

接続文字列を決めたら、実際に接続します。
とはいえ、接続を完全にユーザー側で書くのは大変なので、代行してもらうための存在がおります、それが`DriverManager`クラスです。こいつに接続してもらいましょう。
静的メソッド `getConnection()` を用いて、文字通り **`Connection`を取得する** という振る舞いを求めます。

```java
// (2) DriverManagerクラスのメソッドで接続する
Connection conn = DriverManager.getConnection(uri);
```

生成されたインスタンスはメソッド名の通り`Connection`クラスのため、そのクラスの変数でキャッチしてください。

なおこのメソッドは、接続に失敗した時に送出が創出されます。
対策として、`throws`宣言で親に委譲してしまうか、`try`〜`catch`で対応しましょう。
サンプルコードは`throws`で書いています。

## SQL送信用ステートメントの作成

接続を得ることができたら、その接続にSQLを流し込むための**ステートメント**というものを作成することになります。

```java
// (3) SQL送信用インスタンスの作成
Statement st = conn.createStatement();
```

接続インスタンス(ここでは`conn`という変数で受けてます)に対して「お前のところにSQL送るんで、窓口用意してよ」という感じになります。
`Statement`というクラスのインスタンスになっています。
このインスタンスは繰り返し使えるので、一度作成したら当分保有してることになるでしょう。

## SQLの送信

ステートメントの取得ができれば、ここにSQL文を渡して評価してもらうことができるようになります。
この時に使うのが `execute` 系メソッドになりますが、3種類あります。
適当に選ぶといいでしょう。

### `executeUpdate(SQL文)`

更新系SQLを送信するために使います、更新系といってますが、極端に言えば**SELECT以外**と言ったほうがいいかもしれません。
正常に機能すれば、**何件処理されたか**という戻り値が得られます(整数値)。

### `executeQuery(SQL文)`

結果をテーブルの形で取得するSQLを送信するために使います。ぶっちゃけSELECT文ぐらいです。
正常に機能すれば、表が取得できます。この表は`ResultSet`というクラスのインスタンスになっていますが、この件は別で扱います。

### `execute(SQL文)`

上記2つのメソッドでは、SQLの種類を考えて使い分けする必要がありましたが、**そんなのめんどくせー**という人が使うのがこちらです。
手軽に使えそうですが、クエリの種類(更新系か否か)に応じて、別の結果取得用メソッドを行使する必要があるため、極端に楽になるとかは多分ありません。

### 実際の利用

ここでは更新系を用いているため、`executeUpdate()`を使っています。

```java
// (4) SQL送信
st.executeUpdate("create table sample(id integer primary key)");
...(中略)...
st.executeUpdate("insert into sample values(" + i + ")");
```

前者のコードでは、テーブル作成のクエリを送っています。実際のところテーブル上に変化があるわけではないため、戻り値(数値)はゼロになっています。
後者では、テーブルに値を挿入していますが、文字列をその場で作って送り込んでいます。
この行為に対する功罪は別のところで扱うことになります。

## 利用の終了、解放

利用終了に際しては、正しい手順でクローズしておいたほうがいいです。処理漏れがあるかもしれないので、そういったものをきちんと処理し、正しい手順で終わらせようというわけです。

```java
// (5) 後始末(インスタンスの正常クローズ)
st.close();
conn.close();
```

Javaの場合、ブロックを抜けたらオブジェクトが消滅するといったような明確な基準がない(ガベージコレクトされるまでは生きてるか死んでるか微妙なシュレディンガー状態)ため、デストラクタが期待できません。終了処理をきちんと書くようにしましょう。

# 実行してみると

このコード、実行しようとすると、2つのケースが発生します。

## 正常に実行された場合

正常に動けば、こんな出力になります。

    0: 38ms.
    1000: 1142ms.
    2000: 892ms.
    3000: 748ms.
    4000: 634ms.
    5000: 560ms.
    6000: 595ms.
    7000: 667ms.
    8000: 681ms.
    9000: 537ms.
    所要時間: 7040ms.

1000回ごとのラップが出せるようになっています。もちろんマシンパワーによりかなり変わることでしょう。

## 正常に実行できない場合

コード入力ミスとかを抜きにすれば、たいてい1度はこのエラーに遭遇するでしょう。

    Exception in thread "main" java.sql.SQLException: No suitable driver found 
                                              for jdbc:derby:memory:sample;create=true
    	at java.sql.DriverManager.getConnection(DriverManager.java:689)
    	at java.sql.DriverManager.getConnection(DriverManager.java:270)
    	at dbsample1.Sample1.main(Sample1.java:20)

このエラーは、"No suitable driver found"(適切なドライバーが発見できなかった)というものです。
ドライバーというのはコネクタのことです。
コネクタの読み込みは、実行時に行うことになるため実行前に検査をすることは基本的には無理です。
コネクタはJavaクラスのアーカイブ(jar)形式で配布されており、実行時クラスパスにアーカイブが含まれていないといけません。

Eclipseで行うときは、Derbyのライブラリー(ユーザー・ライブラリー)を作ったほうがいいでしょう。
作ってから、ビルド・パスに加えた上で再実行すればおそらくうまくいきます。

MySQL利用でも同様です。この時もユーザー・ライブラリーにしたほうが作業が楽になることでしょう。