JavaからPostgreSQLへ接続

# はじめに
最近、色々なプログラムからPostgreSQLへ接続する機会が増えたので、手順をまとめてみました。
長くなりそうなので、今回はJavaからPostgreSQLへ接続してみます。
尚、本記事は12/16(土)に開催された[PostgreSQLアンカンファレンス](https://pgunconf.connpass.com/event/71337/)の発表内容を含みます。
当日の資料は[こちら](https://www.slideshare.net/NaoyaTakeuchi/postgresql-84532440/NaoyaTakeuchi/postgresql-84532440)から見れます。

[2018/11/20追記]
今更ながら当日発表した内容にプラスアルファしたC#に関する[記事](https://qiita.com/mimitaro/items/fde451da2f722fe12072)を公開しました。
C#からの接続方法を知りたい方はそちらをご参照ください。

[2019/2/20修正と追記]
@takuya-fujiwara様からコメントを頂き、JDBCドライバのロード箇所を修正しました。
ご指摘ありがとうございます。
修正に関する詳細は[追記](#追記jdbcドライバのロードについて)をご参照ください。

# JavaからPostgreSQLへ接続する。
* 環境情報
<pre>
OS:Windows 10
PostgreSQL: 10.1
Java：1.8
JDBC：4.2
</pre>

PostgreSQLとJavaの環境は導入済みの想定です。
まずJavaからPostgreSQLへ接続するために、PostgreSQLのJDBCドライバを入手します。

* PostgreSQL JDBC Driver<br>
https://jdbc.postgresql.org/download.html

その後Eclipseの場合、パッケージ・エクスプローラから今回作成するプロジェクトを右クリックし、「ビルド・パス」→「ビルド・パスの構成」を選択する。
そして「ライブラリー」タブの「外部JAR追加」を選択し、上記から入手したPostgreSQL JDBCを追加する。
その後、次のようなコードを記述してPostgreSQLへ接続しました。
今回はテストとして、SELECTとINSERT文のトランザクションを実行してみます。

```java:Sample_pos_conn.class
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Sample_pos_conn {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stmt = null;
		ResultSet rset = null;

		//接続文字列
		String url = "jdbc:postgresql://123.45.67.89:5432/postgres";
		String user = "postgres";
		String password = "password";

		try{
			//PostgreSQLへ接続
			conn = DriverManager.getConnection(url, user, password);

			//自動コミットOFF
			conn.setAutoCommit(false);

			//SELECT文の実行
			stmt = conn.createStatement();
			String sql = "SELECT 1";
			rset = stmt.executeQuery(sql);

			//SELECT結果の受け取り
			while(rset.next()){
				String col = rset.getString(1);
				System.out.println(col);
			}

			//INSERT文の実行
			sql = "INSERT INTO jdbc_test VALUES (1, 'AAA')";
			stmt.executeUpdate(sql);
			conn.commit();
		}
		catch (SQLException e){
			e.printStackTrace();
		}
		finally {
			try {
				if(rset != null)rset.close();
				if(stmt != null)stmt.close();
				if(conn != null)conn.close();
			}
			catch (SQLException e){
				e.printStackTrace();
			}

		}
	}
}
```

上記のコードでポイントとなる箇所を順に説明します。


## PostgreSQLへの接続
接続先のデータベースを明示的に指定します。
これはPostgreSQLに接続する場合です(文字列にモロpostgresとか含まれていますし)。
なので他のデータベースへ接続する場合、文字列は当然異なります。
逆に言えば、接続関連の文字列を読み替えれば、同じRDBMSならばJDBCでやれることや動きは似ています。


### 接続文字列

```java:接続文字列
String url = "jdbc:postgresql://<接続先DBサーバのIP or ホスト名>:<DBのポート番号>/<DB名>";
```

接続先のPostgreSQLの情報です。今回はサンプルですが環境毎に上記の情報を記載します。
具体例は最初のサンプルコードをご参照ください。

## SQL文の実行

Java上でSQLを実行する場合、「Statement」と「PreparedStatement」の2つの方法があります。
「Statement」は入力された文字列をそのまま実行する、シンプルなやつです。
「PreparedStatement」事前コンパイルされ高速化され、後からパラメータの変更もできるなかなか優れたやつです。
同じSQLを使いわます場合は基本的に「PreparedStatement」を使います。
ただ今回はシンプルな「Statement」を使います。


### SELECT文

```java:SELECT文
Statement stmt = conn.createStatement();

String sql = "SELECT 1";
rset = stmt.executeQuery(sql);

//SELECT結果の受け取り
while(rset.next()){
	String col = rset.getString(1);
	System.out.println(col);
}
```

SQLを実行するために「Statement 」を「createStatement」メソッドにより初期化します。
おなじないみたいなものですが、「createStatement」メソッドへ渡す引数によってSELECT結果の受け取り方が変わります。
簡単な説明は以下に記載しますが、通常使う場合は引数を指定せず、デフォルト値を使用すればよいと思います。

|第1引数|説明|
|---|---|
|ResultSet.TYPE_FORWARD_ONLY      |デフォルト値。<br>カーソルは順方向にのみ移動可能。<br>ResultSetのlast()、first()等は使用できない。|
|ResultSet.TYPE_FORWARD_ONLY      |カーソルは順方向・逆方向に移動可能。<br>データ取得後に、他の接続によって変更されたデータを反映しない。|
|ResultSet.TYPE_SCROLL_INSENSITIVE|カーソルは順方向・逆方向に移動可能。<br>データ取得後に、他の接続によって変更されたデータを反映する。<br>読み取り一貫性が保証されなくなるため非推奨。|

|第1引数|説明|
|---|---|
|ResultSet.CONCUR_READ_ONLY|デフォルト値。<br>ResultSetによる更新不可。|
|ResultSet.CONCUR_UPDATABLE|ResultSetによる更新可能。<br>updateXXX()等が使用できる。|


SELECT文の結果受け取りには「ResultSet」クラスが使われます。
基本的にはnext()メソッドで、順方向に取得したデータを取り出します。
最後まで取り出した上でnext()メソッドを実行すると、結果がnullになるためwhile文でループして全件取得します。
またResulSetからデータを取り出す時は、列を指定します。
指定の仕方は列の順番 (0番目からスタート)、または列名です。

余談ですが最後だけ、最初だけのデータを取得する場合、last()、first()メソッドを使います。
ただしこの2つは前述した通り、ResultSet.TYPE_FORWARD_ONLYを「createStatement」メソッドの第1引数に渡す必要があります。


### DML (INSERT, DELETE, UPDATE) 文

```java:INSERT文
//INSERT文の実行
sql = "INSERT INTO jdbc_test VALUES (1, 'AAA')";
stmt.executeUpdate(sql);
conn.commit();
```

DMLはSELECTと違って結果を受け取らないのでResultSetを使いません。
そのため「createStatement」メソッド使用時に引数を指定する必要はありません。
ただしSQL文の実行は、「executeUpdate()」メソッドを用いなければいけません。
また後述しますが自動コミットがOFFの場合、commit()メソッドによりコミットします。


### DDL(CREATE ...)文

今回のサンプルには載せていませんが、DML文と同じ方法で実行可能です。
PostgreSQLの場合、DDL文は暗黙コミットされないため、自動コミットがOFFの場合、DML文と同様に明示的なコミットが必要です。

```java:CREATE文
//CREATE文の実行
sql = "CREATE TABLE hoge (col1 integer)";
stmt.executeUpdate(sql);
conn.commit();
```


### COMMIT, ROLLBACK

前述した通りCOMMITやROLLBACKは関数で実行できます。
なので今まで異なりSQL文を記述する必要はありません。

```java:COMMIT文
//COMMITの実行
conn.commit();
```

```java:ROLLBACK文
//ROLLBACKの実行
conn.rollback();
```

## トランザクション

### 自動コミット 

JDBCはデフォルトで自動コミットがONになります。
PostgreSQLを使っている方的には問題ありませんが、他のRDBMSだと注意が必要です。
※PostgreSQLもデフォルトで自動コミットがONなため

自動コミットの有効/無効化はsetAutoCommit()メソッドで行います。
trueの場合有効(デフォルト)、falseの場合無効です。

```java:自動コミットOFF
//自動コミットOFF
conn.setAutoCommit(false);
```

Java上でトランザクション処理をしたい場合、自動コミットを無効化することが一般的です。
SQL文のBEGINを入力することは、後述する理由もあり殆どありません。

### 自動コミットをOFFにした時のトランザクション処理

自動コミットがOFFの場合、SQL開始時に自動的にBEGINをJDBCが入力してくれます。
そのため次のようにBEGINを明示的に記述すると、データベース内では2重のBEGINが実行されます。
COMMIT、ROLLBACKも前述した通り関数で用意されているため、
Javaでトランザクション処理を実施するときは、トランザクションに関わるSQLを入力する機会は殆どないと思います。

```java:BEGIN文の明示的な実行
//BEGIN文の実行
stmt = conn.createStatement();
String sql = "BEGIN";
rset = stmt.executeQuery(sql);
```

```sql:PostgreSQLのログ
LOG:  execute <unnamed>: BEGIN
LOG:  execute <unnamed>: BEGIN
WARNING:  there is already a transaction in progress
```
※PostgreSQLのパラメータlog_statement = 'all'にしています。

# 追記：JDBCドライバのロードについて

JDBC 4.0より昔のバージョンの場合、次のようにClass.forNameによりJDBCドライバの明示的なロードが必要でした。

```java:JDBCドライバのロード
try{
	Class.forName("org.postgresql.Driver");
	…
}
catch (ClassNotFoundException e) {
	…
}
```

上記は、JDBCドライバの種類ごとに記述する文字列は異なります。
こちらを宣言する場合、ClassNotFoundExceptionの例外処理をする必要があります。

ただしJDBC 4.0以降(=Java 1.6以降)では自動でロードするため、上述の手動ロードは必要なくなっています。

> In previous versions of JDBC, to obtain a connection, you first had to initialize your JDBC driver by calling the method Class.forName. This methods required an object of type java.sql.Driver. Each JDBC driver contains one or more classes that implements the interface java.sql.Driver. The drivers for Java DB are org.apache.derby.jdbc.EmbeddedDriver and org.apache.derby.jdbc.ClientDriver, and the one for MySQL Connector/J is com.mysql.jdbc.Driver. See the documentation of your DBMS driver to obtain the name of the class that implements the interface java.sql.Driver.

> Any JDBC 4.0 drivers that are found in your class path are automatically loaded. (However, you must manually load any drivers prior to JDBC 4.0 with the method Class.forName.)

<hr>

> [Google翻訳]
> JDBCの以前のバージョンでは、接続を取得するには、まずClass.forNameメソッドを呼び出してJDBCドライバを初期化する必要がありました。 このメソッドは、java.sql.Driver型のオブジェクトを必要としました。 各JDBCドライバには、インタフェースjava.sql.Driverを実装する1つ以上のクラスが含まれています。 Java DB用のドライバはorg.apache.derby.jdbc.EmbeddedDriverとorg.apache.derby.jdbc.ClientDriverであり、MySQL Connector / J用のドライバはcom.mysql.jdbc.Driverです。 インタフェースjava.sql.Driverを実装するクラスの名前を取得するには、DBMSドライバのマニュアルを参照してください。

> クラスパスにあるJDBC 4.0ドライバはすべて自動的にロードされます。 （ただし、メソッドClass.forNameを使用してJDBC 4.0より前のドライバを手動でロードする必要があります。）

* JDBC Basics Tutorial ～Establishing a Connection～より引用
https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html#drivermanager

* 参考：The PostgreSQL JDBC Interface ～Chapter 3. Initializing the Driver～
https://jdbc.postgresql.org/documentation/head/load.html