Java + MySQL 8.0 で主要な SQL 型の値を取得するサンプルコード

## 概要

- MySQL 8.0 にてテーブルに主要な SQL データ型のカラムを定義して、Java のプログラムからカラムの値を取得するサンプルコードを示す
- 今回の動作確認環境: MySQL Ver 8.0.21 for osx10.15 on x86_64 (Homebrew) + MySQL Connector/J 8.0.21 + Java 14 (AdoptOpenJDK 14.0.2) + Gradle 6.6 + macOS Catalina

## サンプルコード

### ファイル一覧

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

### build.gradle

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

sourceCompatibility = JavaVersion.VERSION_14

repositories {
  mavenCentral()
}

dependencies {
  // 実行時に MySQL Connector/J 8.0.21 を使う
  runtimeOnly 'mysql:mysql-connector-java:8.0.21'
}

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 {

    // MySQL に接続
    String url = "jdbc:mysql://localhost/testdb";
    String user = "foo";
    String password = "cafebabe";
    Connection con = DriverManager.getConnection(url, user, password);
    Statement stmt = con.createStatement();

    // テーブルを作成
    // いろいろな MySQL のデータ型でカラムを定義
    // (Java 14 プレビュー機能で使えるヒアドキュメントっぽく書けるテキストブロック機能を使う)
    stmt.execute("""
      create table test (
        -- 文字列型
        my_char        CHAR(8),        -- 固定長文字列 (最大長 255 文字)
        my_varchar     VARCHAR(1024),  -- 可変長文字列 (最大長 65535 bytes (ただし指定する数値は文字数))
        my_tinytext    TINYTEXT,       -- 可変長文字列 (最大長 255 bytes)
        my_text        TEXT,           -- 可変長文字列 (最大長 65535 bytes)
        my_mediumtext  MEDIUMTEXT,     -- 可変長文字列 (最大長 16777215 bytes)
        my_longtext    LONGTEXT,       -- 可変長文字列 (最大長 4294967295 bytes)

        -- バイナリ型
        my_bit         BIT(16),       -- ビットフィールド値 (最大長 64 bits)
        my_binary      BINARY(4),     -- 固定長バイナリ (最大長 255 bytes)
        my_varbinary   VARBINARY(4),  -- 可変長バイナリ (最大長 65535 bytes)
        my_tinyblob    TINYBLOB,      -- Binary Large Object (最大長 255 bytes)
        my_blob        BLOB,          -- Binary Large Object (最大長 65535 bytes)
        my_mediumblob  MEDIUMBLOB,    -- Binary Large Object (最大長 16777215 bytes)
        my_longblob    LONGBLOB,      -- Binary Large Object (最大長 4294967295 bytes)

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

        -- 整数型
        my_tinyint    TINYINT,    -- 1 byte
        my_smallint   SMALLINT,   -- 2 bytes
        my_mediumint  MEDIUMINT,  -- 3 bytes
        my_integer    INTEGER,    -- 4 bytes
        my_bigint     BIGINT,     -- 8 bytes

        -- 浮動小数点型
        my_float   FLOAT,   -- 単精度浮動小数点数 float 4 bytes
        my_double  DOUBLE,  -- 倍精度浮動小数点数 double 8 bytes

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

        -- 時間型
        my_date       DATE,         -- 年月日
        my_time       TIME,         -- 時分秒
        my_datetime   DATETIME(6),  -- 年月日 + 時分秒 + マイクロ秒
        my_timestamp  TIMESTAMP(6)  -- 年月日 + 時分秒 + マイクロ秒 + タイムゾーン
      ) ENGINE=InnoDB""");

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

        -- バイナリ型
        b'0111111110000000',  -- BIT
        X'CAFEBABE',          -- BINARY,
        X'CAFEBABE',          -- VARBINARY,
        X'CAFEBABE',          -- TINYBLOB,
        X'CAFEBABE',          -- BLOB,
        X'CAFEBABE',          -- MEDIUMBLOB,
        X'CAFEBABE',          -- LONGBLOB,

        -- 真偽値型
        TRUE,  -- BOOLEAN

        -- 整数型
        127                ,  -- TINYINT
        32767              ,  -- SMALLINT
        8388607            ,  -- MEDIUMINT
        2147483647         ,  -- INTEGER
        9223372036854775807,  -- BIGINT

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

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

        -- 時間型
        '2001-02-03',                       -- DATE
        '04:05:06',                         -- TIME
        '9999-12-31 23:59:59.999999',       -- DATETIME
        '2038-01-19 03:14:07.999999+00:00'  -- TIMESTAMP
      )""");

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

      // カラムの JDBC 型や SQL 型に対する 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_tinytext=" + rs.getString("my_tinytext"));
      System.out.println("my_text=" + rs.getString("my_text"));
      System.out.println("my_mediumtext=" + rs.getString("my_mediumtext"));
      System.out.println("my_longtext=" + rs.getString("my_longtext"));

      // バイナリ型
      System.out.println("my_bit=" + Arrays.toString(rs.getBytes("my_bit")));
      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_tinyblob=" + Arrays.toString(rs.getBytes("my_tinyblob")));
      System.out.println("my_blob=" + Arrays.toString(rs.getBytes("my_blob")));
      System.out.println("my_mediumblob=" + Arrays.toString(rs.getBytes("my_mediumblob")));
      System.out.println("my_longblob=" + Arrays.toString(rs.getBytes("my_longblob")));

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

      // 整数型
      System.out.println("my_tinyint=" + rs.getInt("my_tinyint"));
      System.out.println("my_smallint=" + rs.getInt("my_smallint"));
      System.out.println("my_mediumint=" + rs.getInt("my_mediumint"));
      System.out.println("my_integer=" + rs.getInt("my_integer"));
      System.out.println("my_bigint=" + rs.getLong("my_bigint"));

      // 浮動小数点型
      System.out.println("my_float=" + rs.getFloat("my_float"));
      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_datetime=" + rs.getTimestamp("my_datetime"));
      System.out.println("my_timestamp=" + rs.getTimestamp("my_timestamp").toInstant());
    }

    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

> 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_tinytext - VARCHAR - TINYTEXT - java.lang.String
my_text - LONGVARCHAR - TEXT - java.lang.String
my_mediumtext - LONGVARCHAR - MEDIUMTEXT - java.lang.String
my_longtext - LONGVARCHAR - LONGTEXT - java.lang.String
my_bit - BIT - BIT - java.lang.Boolean
my_binary - BINARY - BINARY - [B
my_varbinary - VARBINARY - VARBINARY - [B
my_tinyblob - VARBINARY - TINYBLOB - [B
my_blob - LONGVARBINARY - BLOB - [B
my_mediumblob - LONGVARBINARY - MEDIUMBLOB - [B
my_longblob - LONGVARBINARY - LONGBLOB - [B
my_boolean - BIT - BIT - java.lang.Boolean
my_tinyint - TINYINT - TINYINT - java.lang.Integer
my_smallint - SMALLINT - SMALLINT - java.lang.Integer
my_mediumint - INTEGER - MEDIUMINT - java.lang.Integer
my_integer - INTEGER - INT - java.lang.Integer
my_bigint - BIGINT - BIGINT - java.lang.Long
my_float - REAL - FLOAT - 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_datetime - TIMESTAMP - DATETIME - java.sql.Timestamp
my_timestamp - TIMESTAMP - TIMESTAMP - java.sql.Timestamp

カラム名 - カラムの値
my_char=Hello
my_varchar=Hello
my_tinytext=Hello
my_text=Hello
my_mediumtext=Hello
my_longtext=Hello
my_bit=[127, -128]
my_binary=[-54, -2, -70, -66]
my_varbinary=[-54, -2, -70, -66]
my_tinyblob=[-54, -2, -70, -66]
my_blob=[-54, -2, -70, -66]
my_mediumblob=[-54, -2, -70, -66]
my_longblob=[-54, -2, -70, -66]
my_boolean=true
my_tinyint=127
my_smallint=32767
my_mediumint=8388607
my_integer=2147483647
my_bigint=9223372036854775807
my_float=123.0
my_double=123.0001
my_numeric=123
my_decimal=123
my_date=2001-02-03
my_time=04:05:06
my_datetime=9999-12-31 23:59:59.999999
my_timestamp=2038-01-19T03:14:07.999999Z

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

## 参考資料

- [JDBC API 入門 \- SQL と Java の型のマッピング](https://docs.oracle.com/javase/jp/6/technotes/guides/jdbc/getstart/mapping.html)
- [java\.sql \(Java SE 14 & JDK 14\)](https://docs.oracle.com/javase/jp/14/docs/api/java.sql/module-summary.html)
- [MySQL :: MySQL 8\.0 Reference Manual :: 11 Data Types](https://dev.mysql.com/doc/refman/8.0/en/data-types.html)
- [MySQL :: MySQL 8\.0 Reference Manual :: 11\.1\.2 Integer Types \(Exact Value\) \- INTEGER, INT, SMALLINT, TINYINT, MEDIUMINT, BIGINT](https://dev.mysql.com/doc/refman/8.0/en/integer-types.html)
- [MySQL :: MySQL 8\.0 Reference Manual :: 11\.3\.1 String Data Type Syntax](https://dev.mysql.com/doc/refman/8.0/en/string-type-syntax.html)
- [MySQL :: MySQL 8\.0 Reference Manual :: 11\.7 Data Type Storage Requirements](https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html)
- [MySQL :: MySQL Connector/J 8\.0 Developer Guide :: 6 Connector/J Reference](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference.html)
- [MySQL :: MySQL Connector/J 8\.0 Developer Guide :: 6\.5 Java, JDBC, and MySQL Types](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-type-conversions.html)
- [Java \+ H2 Database で主要な JDBC 型の値を取得するサンプルコード \- Qiita](https://qiita.com/niwasawa/items/266bb877003e40e08d6b)
- [macOS に Homebrew で MySQL 8\.0 をインストールしてデータベースを作成する \- Qiita](https://qiita.com/niwasawa/items/6ffd3e33b3e60b0a8d17)