1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【R2DBC】PostgreSQLでEnum配列を取得するとClassCastExceptionになる問題への対処【jOOQ】

1
Last updated at Posted at 2025-12-16

この記事はJava Advent Calendar 2025 シリーズ 2の記事になりました(Javaが1行も登場しませんが他に適切なカレンダーも見つけられず、ご容赦下さい)。

TL;DR

  • PostgresqlConnectionConfigurationに対し、jOOQで生成されたEnumEnumCodecを登録することで、エラー無く取り扱える

前置き

PostgreSQL上でEnum配列として定義されている列をjOOQで取得すると、Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')というエラーになります。
この問題は以前より知られており、少なくとも1年半以上解決されていません。

この問題に対して見つけた対処方法をまとめます。

バージョン

  • Java: 21
  • r2dbc-postgresql: 1.0.9.RELEASE
  • jOOQ: 3.19.28
  • PostgreSQL: 15.12

やり方

サンプルコードはKotlinになっていますが、Javaでもほぼそのまま書けます。

以下のように、EnumCodecを登録したConnectionFactoryを用いることで実現できます。
YourEnumには、それぞれ処理対象としたいEnumを設定して下さい。

import io.r2dbc.postgresql.PostgresqlConnectionFactory
import io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider
import io.r2dbc.postgresql.codec.EnumCodec
import io.r2dbc.spi.ConnectionFactoryOptions

val url: String = TODO("接続用URL")

val factoryOptions = PostgresqlConnectionFactoryProvider
    .builder(ConnectionFactoryOptions.parse(url))
    .codecRegistrar(
        EnumCodec.builder()
            .withEnum(
                // PostgreSQL上のEnum名。jOOQの生成結果のgetNameと一致するため、適当な値から読み出している
                YourEnum.FOO.getName(),
                YourEnum::class.java
            )
            .withEnum(
                /* 複数登録したい場合はチェーンさせる */
            )
            .build()
    )
    .build()

val connectionFactory = PostgresqlConnectionFactory(factoryOptions)

解説

r2dbc-postgresqlにおけるCodecについて

READMEに書かれている通り、r2dbc-postgresqlでは独自の型マッピングをCodecとして登録することができます。

EnumCodecの配列サポートについて

こちらもREADMEに書かれている通り、EnumCodecは可能なら配列をサポートしてくれます。
jOOQ経由で読み出した際にEnum配列を扱えるようになったのはこの効果によります。

登録の省力化について

サンプルコードでは個別にEnumCodecを登録する方法を紹介しましたが、以下のようにすることで、登録の自動化も可能です。

// テーブル定義一覧からenum配列で定義された列を抽出し、PostgreSQL上のenum定義名とjOOQのenumクラスへ変換
val enumDefinitions: List<Pair<String, Class<Enum<*>>>> = DEFAULT_CATALOG.schemas.flatMap { schema ->
    schema.tables.flatMap { table ->
        table.fields().mapNotNull { field ->
            field
                // 全てのテーブル定義から、enum配列で定義された列のみを抽出
                .takeIf { it.type.isArray && it.type.componentType.isEnum }
                ?.let { _ ->
                    // isEnumのためキャスト可能
                    @Suppress("UNCHECKED_CAST")
                    val enumType = field.type.componentType as Class<Enum<*>>

                    // enumに対するjOOQの生成結果はEnumType.getNameとしてPostgreSQL上のenum定義名を持つため、
                    // 値一覧中の適当な値から当該ゲッターを呼び出している
                    enumType
                        .getEnumConstants()
                        ?.firstOrNull()
                        ?.let { (it as EnumType).name }
                        ?.let { it to enumType }
                }
        }
    }
}

val enumCodecs = enumDefinitions
    .fold(EnumCodec.builder()) { acc, (name, clazz) ->
        acc.withEnum(name, clazz)
    }
    .build()

val factoryOptions = PostgresqlConnectionFactoryProvider
    .builder(ConnectionFactoryOptions.parse(r2dbcProperties.url))
    // 生成したコーデックを登録
    .codecRegistrar(enumCodecs)
    .build()

val connectionFactory = PostgresqlConnectionFactory(factoryOptions)

全テーブル・全列を舐めることになるため、初期化速度は低下しますが、それを無視できる場合にはお試し下さい。

関連

同じような読み出し時の問題で、jOOQ側に設定を追加することで解消できたケースも有ったため、関連として貼ります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?