JavaでTINYINTカラムを扱う時は注意が必要かも

ふだんDB操作まわりのコードはローカル上でH2のオンメモリDBを使ったテストを通してからdevelopブランチに上げるようにしている。

なのでdevelopでDB操作がコケる事はあまりないんだけど、先日developをデプロイした環境(DBはMySQL)でDBから取ってきた値の型がテスト時のもの異なっているという不思議現象に出くわした。

問題のテーブルではユーザの会員状態を表すTINYINTの"status"カラムがあって、こんなコードで取ってきている。

Object value = resultSet.getObject();

コードはカラム値がByteで取得できる事を期待して実装されていた。ここでByteが返ってくる事はJDBC APIリファレンスでも言及されている。

docs.oracle.com/javase/jp/6/technotes/guides/jdbc/getstart/mapping.html#999493
JDBC TINYINT 型に対して推奨されている Java マッピングは、Java byte か Java short のどちらかです。8 ビットの Java byte 型は -128 から 127 までの符号付きの値を表すので、より大きな TINYINT 値に対して常に適切になるとは限りません。 ただし、16 ビットの Java short は常にすべての TINYINT 値を保持することができます。

実際これはH2上でテストした際は問題なく動いていたんだけど、MySQLを使った環境ではなぜかvalueにInteger型が入っていて動作がおかしくなっていた。

調べてみると、どうもJDBCドライバではResultSet#getObject()の挙動が微妙に違うらしい。

MySQL :: MySQL 5.0 Reference Manual :: 25.4.4.3 Java, JDBC and MySQL Types
MySQL Types to Java Types for ResultSet.getObject(). TINYINT java.lang.Boolean if the configuration property tinyInt1isBit is set to true (the default) and the storage size is 1, or java.lang.Integer if not.

TINYINTカラムをgetObject()で取り出した値は通常Integer型になるとのこと。そもそもByteには何をしてもなり得ないらしい。単体テストで使っていたH2用のドライバではきちんとgetObject()でByte型を返していた為テストが通っていたらしい。

今回は実装の都合上あえてgetObjectを使っていたけど、予め型がわかっている場合はgetByte()とか別メソッドを使ったほうがドライバ依存でハマらずに済んで良いのかもしれない。

JDBCドライバがJDBCリファレンスに背いた挙動取ってるし、厳しい