TL;DR
-
Settings.isBindOffsetDateTimeType
がfalse
になっていた場合、jOOQ
はR2DBC
から文字列で情報を読み出し、OffsetDateTime
へパースしようとする - 一方、
r2dbc-postgresql
はTIMESTAMP WITH TIME ZONE
を文字列へデコードする手段を提供していないため、読み出しが失敗する - 読み出し処理時に
Settings.isBindOffsetDateTimeType
がtrue
になるよう設定することでこの問題は解決する
状況
Spring Boot
+ R2DBC
+ jOOQ
のプロジェクトにて、H2
からPostgres
への移行を行なっていた所、OffsetDateTime
を読み出す処理が何も変更していないのに失敗するようになりました。
問題となった部分のスタックトレースは以下のようになっていました。
Caused by: java.lang.IllegalArgumentException: Cannot decode value of type java.lang.String with OID 1184
at io.r2dbc.postgresql.codec.DefaultCodecs.decode(DefaultCodecs.java:158) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
at io.r2dbc.postgresql.PostgresqlRow.decode(PostgresqlRow.java:90) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
at io.r2dbc.postgresql.PostgresqlRow.get(PostgresqlRow.java:67) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
at org.jooq.impl.R2DBC$R2DBCResultSet$DefaultRow.get(R2DBC.java:1110) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.R2DBC$R2DBCResultSet.nullable(R2DBC.java:989) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.R2DBC$R2DBCResultSet.nullable(R2DBC.java:985) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.R2DBC$R2DBCResultSet.getString(R2DBC.java:1050) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.DefaultBinding$DefaultOffsetDateTimeBinding.get0(DefaultBinding.java:3085) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.DefaultBinding$DefaultOffsetDateTimeBinding.get0(DefaultBinding.java:3000) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.DefaultBinding$AbstractBinding.get(DefaultBinding.java:942) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.R2DBC$ResultSubscriber.lambda$onNext$0(R2DBC.java:327) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.RecordDelegate.operate(RecordDelegate.java:143) ~[jooq-3.15.1.jar:na]
at org.jooq.impl.R2DBC$ResultSubscriber.lambda$onNext$1(R2DBC.java:313) ~[jooq-3.15.1.jar:na]
at io.r2dbc.postgresql.PostgresqlResult.lambda$map$1(PostgresqlResult.java:111) ~[r2dbc-postgresql-0.8.8.RELEASE.jar:0.8.8.RELEASE]
ここで、OID 1184
はio.r2dbc.postgresql.type.PostgresqlObjectId.TIMESTAMPTZ
(= TIMESTAMP WITH TIME ZONE
)を表します。
従って、このエラーメッセージは「TIMESTAMP WITH TIME ZONE
をjava.lang.String
として読み出せなかった」ということを意味します。
原因
コードを追いかけた所、org.jooq.impl.DefaultBinding.DefaultOffsetDateTimeBinding.get0
の関数がgetString
で情報を読み出していることが直接の原因でした。
実際のコードは以下の通りです。
@Override
final OffsetDateTime get0(BindingGetResultSetContext<U> ctx) throws SQLException {
if (!FALSE.equals(ctx.settings().isBindOffsetDateTimeType()))
return ctx.resultSet().getObject(ctx.index(), OffsetDateTime.class);
else
return OffsetDateTimeParser.offsetDateTime(ctx.resultSet().getString(ctx.index()));
}
対処
先ほどお見せしたコードで、getObject
が呼ばれるようにする、つまりSettings.isBindOffsetDateTimeType
にtrue
をセットすることで対処できました。
具体的な設定方法は幾つか考えられますが、自分はBean
初期化時に以下のようにすることで対処しました。
import io.r2dbc.spi.ConnectionFactory
import org.jooq.DSLContext
import org.jooq.SQLDialect
import org.jooq.conf.Settings
import org.jooq.impl.DSL
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component
@Component
class Config(val cfi: ConnectionFactory) {
@Bean
fun jooqDSLContext(): DSLContext = DSL.using(
cfi,
SQLDialect.POSTGRES,
Settings().apply { isBindOffsetDateTimeType = true }
).dsl()
}
この設定方法に関しては自信が無いため、より良い方法などあればご教示頂けると幸いです。