Java + H2 Database で主要な JDBC 型の値を取得するサンプルコード

## 概要

- H2 Database にてテーブルに主要な JDBC 型となる SQL データ型のカラムを定義して、Java のプログラムからカラムの値を取得するサンプルコードを示す
- 今回の動作確認環境: H2 Database 1.4.200 + Java 14 (AdoptOpenJDK 14.0.2) + Gradle 6.6 + macOS Catalina

## JDBC 型とは

JDBC 型は SQL 型と Java 言語の型の間を取り持つ型。SQL のデータを表す型 (CHAR や INTEGER など) に近いもの。

SQL 型 ←(マッピング)→ JDBC 型 ←(マッピング)→ Java 言語型

[JDBC API 入門 \- SQL と Java の型のマッピング](https://docs.oracle.com/javase/jp/6/technotes/guides/jdbc/getstart/mapping.html)

> 異なるデータベース製品がサポートする SQL の型の間には、相当な相違があります。異なるデータベースが同一の意味を持つ SQL の型をサポートしている場合でも、それらの型に異なる名前を与えていることがあります。たとえば、主要データベースのほとんどが大きなバイナリ値に対する SQL の型をサポートしていますが、Oracle ではこの型を LONG RAW、Sybase では IMAGE、Informix では BYTE、DB2 では LONG VARCHAR FOR BIT DATA とそれぞれ呼んでいます。
> 
> JDBC プログラマは、通常は、ターゲットのデータベースが使用している実際の SQL の型名に気を使う必要はありません。多くの場合、JDBC プログラマは、既存のデータベースのテーブルに対してプログラミングをし、そうしたテーブルを作成した正確な SQL の型名に注意を払う必要はありません。
> 
> JDBC は、クラス java.sql.Types で総称 SQL の型識別子のセットを定義しています。そのセットの型は、もっとも一般的に使用される SQL の型を表すように設計されています。JDBC API によるプログラミングでは、プログラマは通常、ターゲットのデータベースが使用している正確な SQL の型名を意識することなく、そのセットの JDBC 型を使用して総称 SQL の型を参照することができます。

## サンプルコード

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        └── java
            └── JdbcSample.java
```

### build.gradle

```groovy
plugins {
  id 'application'
  id 'java'
}

sourceCompatibility = JavaVersion.VERSION_14

repositories {
  mavenCentral()
}

dependencies {
  // 実行時に H2 Database 1.4.200 を使う
  runtimeOnly 'com.h2database:h2:1.4.200'
}

tasks.withType(JavaCompile) {
  // Java 14 のプレビュー機能を使う
  options.compilerArgs += ['--enable-preview']
}

application {
  // Java 14 のプレビュー機能を使う
  applicationDefaultJvmArgs = ['--enable-preview']
  mainClassName = 'JdbcSample'
}
```

### JdbcSample.java

```java
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.sql.Types;
import java.util.Arrays;

class JdbcSample {

  public static void main(String[] args) throws Exception {

    // H2 Database に接続
    // mem: インメモリデータベース化
    // DB_CLOSE_DELAY=-1: コネクション切断時にDBコンテンツを削除しない
    String url = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
    String user = "sa";
    String password = "";
    Connection con = DriverManager.getConnection(url, user, password);
    Statement stmt = con.createStatement();

    // テーブルを作成
    // いろいろな SQL データ型でカラムを定義
    // (Java 14 プレビュー機能で使えるヒアドキュメントっぽく書けるテキストブロック機能を使う)
    stmt.execute("""
      create table test (
        -- 文字列型
        my_char         CHAR,         -- 短い固定長文字列
        my_varchar      VARCHAR,      -- 短い可変長文字列
        my_longvarchar  LONGVARCHAR,  -- 長い可変長文字列
        my_clob         CLOB,         -- Character Large Object

        -- バイナリ型
        my_binary         BINARY(4),         -- 小さな固定長バイナリ (SQL データ型の BIT)
        my_varbinary      VARBINARY(4),      -- 小さな可変長バイナリ (SQL データ型の BIT VARYING)
        my_longvarbinary  LONGVARBINARY(4),  -- 大きな可変長バイナリ
        my_blob           BLOB(4),           -- Binary Large Object

        -- 真偽値型
        my_boolean  BOOLEAN,  -- 真偽値

        -- 整数型
        my_smallint  SMALLINT,  -- short
        my_integer   INTEGER,   -- int
        my_bigint    BIGINT,    -- long

        -- 浮動小数点型
        my_real    REAL,    -- 単精度浮動小数点数 float
        my_double  DOUBLE,  -- 倍精度浮動小数点数 double

        -- 固定小数点型
        my_numeric  NUMERIC,  -- 固定小数点数
        my_decimal  DECIMAL,  -- 固定小数点型

        -- 時間型
        my_date       DATE,       -- 年月日
        my_time       TIME,       -- 時分秒
        my_timestamp  TIMESTAMP   -- 年月日 + 時分秒 + ナノ秒
      )""");

    // レコードを追加
    // (Java 14 プレビュー機能で使えるヒアドキュメントっぽく書けるテキストブロック機能を使う)
    stmt.execute("""
      insert into test values (
        -- 文字列型
        'Hello', -- CHAR
        'Hello', -- VARCHAR
        'Hello', -- LONGVARCHAR
        'Hello', -- CLOB

        -- バイナリ型
        X'CAFEBABE',  -- BINARY,
        X'CAFEBABE',  -- VARBINARY,
        X'CAFEBABE',  -- LONGVARBINARY,
        X'CAFEBABE',  -- BLOB,

        -- 真偽値型
        TRUE,  -- BOOLEAN

        -- 整数型
        32767              ,  -- SMALLINT
        2147483647         ,  -- INTEGER
        9223372036854775807,  -- BIGINT

        -- 浮動小数点型
        123.0001,  -- REAL
        123.0001,  -- DOUBLE

        -- 固定小数点型
        123.0001,  -- NUMERIC
        123.0001,  -- DECIMAL

        -- 時間型
        '2001-02-03',                     -- DATE
        '04:05:06',                       -- TIME
        '2001-02-03 04:05:06.123456789'   -- TIMESTAMP
      )""");

    // レコードを取得
    ResultSet rs = stmt.executeQuery("select * from test");
    while (rs.next()) {

      // カラムの JDBC 型に対する Java オブジェクトの型を取得
      System.out.println("カラム名 - JDBC 型 - データベース固有の SQL 型 - Java オブジェクトの型");
      ResultSetMetaData rsmd = rs.getMetaData();
      for (int i = 1; i <= rsmd.getColumnCount(); i++) {
        System.out.println(
          rsmd.getColumnName(i) + " - " +
          getJdbcTypeName(rsmd.getColumnType(i)) + " - " +
          rsmd.getColumnTypeName(i) + " - " +
          rsmd.getColumnClassName(i));
      }
      System.out.println();

      // カラムの値を取得していく
      System.out.println("カラム名 - カラムの値");

      // 文字列型
      System.out.println("my_char=" + rs.getString("my_char"));
      System.out.println("my_varchar=" + rs.getString("my_varchar"));
      System.out.println("my_longvarchar=" + rs.getString("my_longvarchar"));
      System.out.println("my_clob=" + rs.getClob("my_clob"));

      // バイナリ型
      System.out.println("my_binary=" + Arrays.toString(rs.getBytes("my_binary")));
      System.out.println("my_varbinary=" + Arrays.toString(rs.getBytes("my_varbinary")));
      System.out.println("my_longvarbinary=" + Arrays.toString(rs.getBytes("my_longvarbinary")));
      System.out.println("my_blob=" + rs.getBlob("my_blob"));

      // 真偽値型
      System.out.println("my_boolean=" + rs.getBoolean("my_boolean"));

      // 整数型
      System.out.println("my_smallint=" + rs.getShort("my_smallint"));
      System.out.println("my_integer=" + rs.getInt("my_integer"));
      System.out.println("my_bigint=" + rs.getBigDecimal("my_bigint"));

      // 浮動小数点型
      System.out.println("my_real=" + rs.getFloat("my_real"));
      System.out.println("my_double=" + rs.getDouble("my_double"));

      // 固定小数点型
      System.out.println("my_numeric=" + rs.getBigDecimal("my_numeric"));
      System.out.println("my_decimal=" + rs.getBigDecimal("my_decimal"));

      // 時間型
      System.out.println("my_date=" + rs.getDate("my_date"));
      System.out.println("my_time=" + rs.getTime("my_time"));
      System.out.println("my_timestamp=" + rs.getTimestamp("my_timestamp"));
    }

    stmt.close();
    con.close();
  }

  // JDBC 型の名称を取得する
  private static String getJdbcTypeName(int type) throws IllegalAccessException {
    Field[] fs = Types.class.getDeclaredFields();
    for (Field f : fs) {
      if (type == f.getInt(null)) {
        return f.getName();
      }
    }
    return null;
  }
}
```

## 実行結果

Gradle の run タスクで実行。

```
$ gradle run
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :compileJava
注意:/Users/foo/bar/src/main/java/JdbcSample.javaはプレビュー言語機能を使用します。
注意:詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。

> Task :run
カラム名 - JDBC 型 - データベース固有の SQL 型 - Java オブジェクトの型
MY_CHAR - CHAR - CHAR - java.lang.String
MY_VARCHAR - VARCHAR - VARCHAR - java.lang.String
MY_LONGVARCHAR - VARCHAR - VARCHAR - java.lang.String
MY_CLOB - CLOB - CLOB - java.sql.Clob
MY_BINARY - VARBINARY - VARBINARY - [B
MY_VARBINARY - VARBINARY - VARBINARY - [B
MY_LONGVARBINARY - VARBINARY - VARBINARY - [B
MY_BLOB - BLOB - BLOB - java.sql.Blob
MY_BOOLEAN - BOOLEAN - BOOLEAN - java.lang.Boolean
MY_SMALLINT - SMALLINT - SMALLINT - java.lang.Short
MY_INTEGER - INTEGER - INTEGER - java.lang.Integer
MY_BIGINT - BIGINT - BIGINT - java.lang.Long
MY_REAL - REAL - REAL - java.lang.Float
MY_DOUBLE - DOUBLE - DOUBLE - java.lang.Double
MY_NUMERIC - DECIMAL - DECIMAL - java.math.BigDecimal
MY_DECIMAL - DECIMAL - DECIMAL - java.math.BigDecimal
MY_DATE - DATE - DATE - java.sql.Date
MY_TIME - TIME - TIME - java.sql.Time
MY_TIMESTAMP - TIMESTAMP - TIMESTAMP - java.sql.Timestamp

カラム名 - カラムの値
my_char=Hello
my_varchar=Hello
my_longvarchar=Hello
my_clob=clob0: 'Hello'
my_binary=[-54, -2, -70, -66]
my_varbinary=[-54, -2, -70, -66]
my_longvarbinary=[-54, -2, -70, -66]
my_blob=blob0: X'cafebabe'
my_boolean=true
my_smallint=32767
my_integer=2147483647
my_bigint=9223372036854775807
my_real=123.0001
my_double=123.0001
my_numeric=123.0001
my_decimal=123.0001
my_date=2001-02-03
my_time=04:05:06
my_timestamp=2001-02-03 04:05:06.123457

BUILD SUCCESSFUL in 6s
2 actionable tasks: 2 executed
```

## 参考資料

- [JDBC API 入門 \- SQL と Java の型のマッピング](https://docs.oracle.com/javase/jp/6/technotes/guides/jdbc/getstart/mapping.html)
- [Getting Started with the JDBC API](https://docs.oracle.com/javase/6/docs/technotes/guides/jdbc/getstart/GettingStartedTOC.fm.html)
- [java\.sql \(Java SE 14 & JDK 14\)](https://docs.oracle.com/javase/jp/14/docs/api/java.sql/module-summary.html)
- [The Java Community Process\(SM\) Program \- JSRs: Java Specification Requests \- detail JSR\# 221: JDBC 4.0 API Specification](https://jcp.org/en/jsr/detail?id=221)
- [H2 Database Engine \- Data Types](http://www.h2database.com/html/datatypes.html)
- [新しい業界標準「SQL99」詳細解説](https://www.atmarkit.co.jp/fnetwork/tokusyuu/01sql99/sql99_0.html)