【トラブル】【Java】 CHAR(1)の項目を文字で更新すると、SQLException: Incorrect string value ... が 発生する

問題概要

 * 以下の環境下で、CHAR(1)データ項目を文字(例えば 'f')で更新処理を行ったところ、
   SQLException: Incorrect string value: 'xxx' for column 'yyy'が 発生した

環境

 * OS : Windows7/10
 * Java : Java1.8
 * DB : MySQL
 * dbutils-1.6

エラー内容

java.sql.SQLException: Incorrect string value: '\xAC\xED\x00\x05sr...' for column 'sex' at row 1 Query: INSERT INTO person (id, name, sex) VALUES (?, ?, ?) Parameters: [Z0000001, Tommy, f]
	at org.apache.commons.dbutils.AbstractQueryRunner.rethrow(AbstractQueryRunner.java:392)
	at org.apache.commons.dbutils.QueryRunner.update(QueryRunner.java:491)
	at org.apache.commons.dbutils.QueryRunner.update(QueryRunner.java:404)
	at com.sample.db.SampleDbUtils.execute(SampleDbUtils.java:28)
	at com.sample.db.SampleDbUtils.main(SampleDbUtils.java:14)

サンプル

テーブル

CREATE TABLE `person` (
	`id` CHAR(8) NOT NULL,
	`name` VARCHAR(100) NULL DEFAULT NULL,
	`sex` CHAR(1) NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

NG例

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

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;

public class SampleDbUtils {

  public static void main(String[] args) {
    SampleDbUtils sample = new SampleDbUtils();
    sample.execute();
  }

  Connection connection = null;
  QueryRunner queryRunner = new QueryRunner();

  public void execute() {
    try {
      // DB接続
      this.connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/sampledb", "root", "password");

      // オートコミットを無効
      this.connection.setAutoCommit(false);
      // INSERTサンプル
      this.queryRunner.update(this.connection, "INSERT INTO person (id, name, sex) VALUES (?, ?, ?)", "Z0000001",
          "Tommy", 'f');
      // commit
      DbUtils.commitAndCloseQuietly(this.connection);
    } catch (SQLException ex) {
      DbUtils.rollbackAndCloseQuietly(this.connection);
      ex.printStackTrace();
    } finally {
      DbUtils.closeQuietly(this.connection);
    }
  }
}

原因

 * 文字 'f' を設定したこと

調査詳細

DbUtils内の[AbstractQueryRunner.java]のfillStatement()より
~~~
public void fillStatement(PreparedStatement stmt, Object... params)
  // 略

  if (params[i] != null) {
    stmt.setObject(i + 1, params[i]);
  } else {
~~~
params[i]が文字(例 'f')の場合、うまくいかない

試しに、DbUtilsを使わずに、以下のようにPreparedStatement を使っても同じエラーが発生する
~~~
PreparedStatement preparedStatement = 
      connection.prepareStatement("INSERT INTO person(id, name, sex) VALUES(?, ?, ?)");) {
preparedStatement.setString(1, "Z0000001");
preparedStatement.setString(2, "Tom");
preparedStatement.setObject(3, 'f');
result = preparedStatement.executeUpdate();
~~~

解決策

 * 文字 CHAR(1)の項目であっても、文字 'f' ではなく、文字列 "f" として設定する

~~~
this.queryRunner.update(this.connection, "INSERT INTO person (id, name, sex) VALUES (?, ?, ?)",
 "Z0000001", "Tommy", "f");

preparedStatement.setString(3, "f");
~~~

後日談

http://stackoverflow.com/questions/5686514/jdbc-how-to-set-char-in-a-prepared-statement
で同じようなこと相談してた。上記より一部抜粋。


JDBC Type              Java Type
-------------------------------------------
CHAR                   String
VARCHAR                String
LONGVARCHAR            String
NUMERIC                java.math.BigDecimal
DECIMAL                java.math.BigDecimal
BIT                    boolean
BOOLEAN                boolean
TINYINT                byte
SMALLINT               short