Search code examples
spring-bootkotlinspring-data-r2dbcr2dbc

Spring R2DBC: How to replace deprecated DatabaseClient.as(…) and handle custom types/converters for jsonb field


I have a problem with objects mapping after migrating to Spring Boot 2.4. So far I was using Spring Boot 2.3 (with Kotlin).

For the sample database query

    override fun findMyEntities(searchParams: SearchParams): Flux<MyEntity> =
        databaseClient
            .sql("SELECT *  FROM my_entities ........................... OFFSET :offset LIMIT :limit")
            .bind("searchPhrase", searchParams.searchPhrase)
            .bind("limit", searchParams.pageSize)
            .bind("offset", searchParams.offset)
            .map(::mapRow)
            .all()

    private fun mapRow(row: Row) = MyEntity(
        id = row.get("id", UUID::class.java)!!,
        name = row.get("name", String::class.java)!!,
        description = row.get("description", String::class.java)!!,
        creation = row.get("creation", UserActionMetadata::class.java)!!,
    )

I'm getting errors:

Suppressed: java.lang.IllegalArgumentException: Cannot decode value of type com.mypackage.model.UserActionMetadata
        at io.r2dbc.postgresql.codec.DefaultCodecs.decode(DefaultCodecs.java:153)
        at io.r2dbc.postgresql.PostgresqlRow.decode(PostgresqlRow.java:90)
        at io.r2dbc.postgresql.PostgresqlRow.get(PostgresqlRow.java:77)
        at com.mypackage.repository.MyRepotistory.mapRow(MyRepotistory.kt:59)

Before version upgrade function above was like:

.execute("SELECT * FROM ...")
.`as`(MyEntity::class.java)
.fetch()
.all()

Where the error is happening, creation field in database is JSONB type. For that I have custom reading and writing converters (in my configuration class which extends AbstractR2dbcConfiguration).

import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter

@Configuration
class DatabaseConfig(private val r2dbcProperties: R2dbcProperties, private val objectMapper: ObjectMapper) :
    AbstractR2dbcConfiguration() {

    @WritingConverter
    inner class UserActionMetadataToJsonConverter : Converter<UserActionMetadata, Json> {
        override fun convert(source: UserActionMetadata): Json {
            return Json.of(objectMapper.writeValueAsString(source))
        }
    }

    @ReadingConverter
    inner class JsonToUserActionMetadataConverter : Converter<Json, UserActionMetadata> {
        override fun convert(source: Json): UserActionMetadata {
            return objectMapper.readValue(source.asString())
        }
    }

Any idea how I can successfully migrate to Spring Boot 2.4?

I have seen suggestions Spring R2DBC DatabaseClient.as(…) to use R2dbcEntityTemplate, but my queries are too complex, and code will be too bloated. There is also an issue https://github.com/spring-projects/spring-framework/issues/26021 . I hope that there is some workaround.


Solution

  • Following code converts the data into entities:

    import org.springframework.r2dbc.core.DatabaseClient
    import org.springframework.data.r2dbc.convert.MappingR2dbcConverter
    
    class MyRepository(
        private val databaseClient: DatabaseClient,
        private val converter: MappingR2dbcConverter
    ) {
       fun fetchMyEntity() = databaseClient
           .sql("...")
           .map { row, metadata -> converter.read(MyEntity::class.java, row, metadata) }
           .all()
    }