結論
java.sql.ResultSet
の動作をちゃんと押さえておきましょう。
うっかりハマった実装
NULL値
を許容する整数型カラム(JdbcType=INTEGER
)を扱うTypeHandler を作成していて、NULL値
で insert したレコードを select した結果が 0
になってしまう事象に悩まされること小一時間。
public class HogeTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, toInteger(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) therows SQLException {
return toString(rs.getInt(columnName));
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) therows SQLException {
return toString(rs.getInt(columnIndex));
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) therows SQLException {
return toString(cs.getInt(columnIndex));
}
private String toString(Integer parameter) {
// omitted
}
private Integer toInteger(String parameter) {
// omitted
}
}
デバッグで追ってみると、insert の際は直接 SQL 文にバインドされてレコード上は NULL値
となっているのに対し、select 時は TypeHandler が呼び出され rs.getInt(columnName)
が 0
を返してくる???
ResultSet (Java Platform SE 8) の getInt メソッド
このResultSetオブジェクトの現在行にある指定された列の値を、Javaプログラミング言語のintとして取得します。
...
戻り値:
列値。値がSQL NULLの場合、返される値は0
ぐはっ、戻り値は Integer
ではなく int
だと。。。
(しかも唾棄すべき無意味な オートボクシング
までさせていたとは。。。)
期待通りに動いた実装
public class FugaTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setBigDecimal(i, toBigDecimal(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) therows SQLException {
return toString(rs.getBigDecimal(columnName));
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) therows SQLException {
return toString(rs.getBigDecimal(columnIndex));
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) therows SQLException {
return toString(cs.getBigDecimal(columnIndex));
}
private String toString(BigDecimal parameter) {
// omitted
}
private BigDecimal toBigDecimal(String parameter) {
// omitted
}
}
ResultSet (Java Platform SE 8) の getBigDecimal メソッド
このResultSetオブジェクトの現在行にある指定された列の値を、完全な精度のjava.math.BigDecimalとして取得します。
...
戻り値:
全精度の列値。値がSQL NULLの場合、返される値はJavaプログラミング言語のnull
BigDecimal
で扱うので、(今回の要件だった)文字列化したときに小数点以下の値が付いてしまうことを危惧して BigDecimal#stripTrailingZeros()
で精度調整するようにしてみたけれど、整数値を渡した場合の scale
は 0
にしてくれるみたいです。
(2018/6/15 追記)
@pale2f に教えていただいた ResultSet#wasNull()
を使って、実装を見直してみました。
ちなみに PreparedStatement#setInt()
では NULL値
を設定できないので PreparedStatement#setNull()
との使い分けが必要になりましたが、BigDecimal
が出てこなくなってスッキリしました。
より良いと思われる実装
public class PiyoTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null && !parameter.equals("")) {
ps.setInt(i, Integer.parseInt(parameter));
} else {
ps.setNull(i, Types.INTEGER);
}
}
@Override
public String getNullableResult(ResultSet rs, String columnName) therows SQLException {
int value = rs.getInt(columnName);
return toString(value, rs.wasNull());
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) therows SQLException {
int value = rs.getInt(columnIndex);
return toString(value, rs.wasNull());
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) therows SQLException {
int value = cs.getInt(columnIndex);
return toString(value, cs.wasNull());
}
private String toString(int parameter, boolean wasNull) {
if (wasNull) {
return "";
}
return String.valueOf(parameter);
}
}
(2018/6/15 追記 その2)
うっかりしてましたが、本事象は BaseTypeHandler<T>
を素直に継承した場合は起こり得ません。
NULL値
だった場合に空文字列を返すという要件のために、以下の 3 メソッドをオーバーライドしてました。
public T getResult(ResultSet rs, String columnName)
public T getResult(ResultSet rs, int columnIndex)
public T getResult(CallableStatement cs, String columnIndex)
// omitted
if (rs.wasNull()) {
return null;
} else {
return result;
}
// omitted
return result;
つまり、元の実装ではちゃんと NULL値
かどうかの判定をしてくれていた
のに、自らそれを取り除いてしまったため、こんな捩れた実装になってしまったようですorz
(しかもここで ResultSet#wasNull()
出てきてるし。。。)