TL;DR
-
BeanPropertyRowMapper/DataClassRowMapper/SingleColumnRowMapperはConversionServiceを登録することで値の変換ができる1 - 何も指定していない状態でも、
DefaultConversionService.getSharedInstance()の取得結果が利用されている
本文
SpringFrameworkにはConversionServiceを用いた型変換の仕組みがデフォルトで用意されており、RowMapperの中でもそれを利用した型変換を行うことができます。
以下のように、数値で年月を表したカラムをjava.time.YearMonthに変換して取得する場面を例にします。
CREATE TABLE IF NOT EXISTS `foo_table` (
`year_month` MEDIUMINT UNSIGNED NOT NULL,
PRIMARY KEY (`year_month`)
);
以下のように、何も設定せずに取得処理を行うとエラーが発生します。
SingleColumnRowMapper<YearMonth> mapper = new SingleColumnRowMapper<>(YearMonth.class);
// この取得処理でTypeMismatchDataAccessExceptionが発生する
YearMonth actual = template.queryForObject("SELECT year_month FROM foo_table", mapper);
ConversionServiceを設定することで、この取得処理を行えるようにします。
今回はDefaultConversionServiceにConverterを設定し、更にそれをRowMapperに登録することでこれを行う例を示します2。
SingleColumnRowMapper<YearMonth> mapper = new SingleColumnRowMapper<>(YearMonth.class);
// Converterの登録処理
DefaultConversionService defaultConversionService = new DefaultConversionService();
defaultConversionService.addConverter(new Converter<Integer, YearMonth>() {
@Override
public YearMonth convert(@NotNull Integer source) {
return YearMonth.of(source / 100, source % 100);
}
});
mapper.setConversionService(defaultConversionService);
YearMonth actual = template.queryForObject("SELECT year_month FROM foo_table", mapper);
assertEquals(YearMonth.of(2021, 1), actual);
補足: DefaultConversionServiceについて
Spring全体で、ConversionServiceを設定しなかった場合はDefaultConversionService.getSharedInstance()で取得されるstaticなConversionServiceが利用されます。
このインスタンスにConverterを設定すれば、デフォルトではアプリケーション全体でそれを使い回すことができます。
DefaultConversionService sharedInstance =
(DefaultConversionService) DefaultConversionService.getSharedInstance();
sharedInstance.addConverter(new Converter<Integer, YearMonth>() {
@Override
public YearMonth convert(@NotNull Integer source) {
return YearMonth.of(source / 100, source % 100);
}
});
また、SpringFramework内でString -> Enumのような変換が行われるのはこのsharedInstanceの働きによるものです。
どのような変換が行われるかはソースコードから読み取ることができます。