Converter<Row, Entity> for spring data r2dbc not working

118 Views Asked by At

I try to implement one-to-one relationship query in @Query. The relevant table structure is as follows

@Table("t_author")
data class Author(
    @field:Id
    val id: Long,
    var name: String,
)

@Table("t_book")
data class Book(
    @field:Id
    val id: Long,
    var title: String,
    var authorId: Long,
    var publishTime: Instant,
    var author: Author? // Associated 't_author'
)

My query is defined as follows

@Repository
interface BookRepository : R2dbcRepository<Book, Long> {

    @Query(
        """
        SELECT b.*, a.name AS author_name 
        FROM t_book b
        LEFT JOIN t_author a ON b.author_id = a.id
        WHERE b.id = :id
    """
    )
    fun bookAndAuthorById(@Param("id") id: Long): Mono<Book>
}

I try to map the return result of the above sql to Book.class, I try to define a converter.

import io.r2dbc.spi.Row
import org.springframework.core.convert.converter.Converter
import org.springframework.data.convert.ReadingConverter
import java.time.Instant


@ReadingConverter
class BookConverter : Converter<Row, Book> {
    override fun convert(source: Row): Book {

        println("BookConverter.convert()")

        return Book(
            id = source.get("id", Long::class.java)!!,
            title = source.get("title", String::class.java)!!,
            publishTime = source.get("publish", Instant::class.java)!!,
            authorId = source.get("author_id", Long::class.java)!!,
            author = Author(
                id = source.get("author_id", Long::class.java)!!,
                name = source.get("author_name", String::class.java)!!
            )
        )
    }
}

and use in configuration class,

@EnableR2dbcRepositories
@Configuration
class R2DbcConfig {

    @Bean
    @ConditionalOnMissingBean
    fun r2dbcCustomConversions(): R2dbcCustomConversions {

        println("R2DbcConfig.r2dbcCustomConversions()")

        return R2dbcCustomConversions.of(
            MySqlDialect.INSTANCE,
            BookConverter() // use
        )
    }
}

Finally, my test results are as follows,

@field:Autowired
lateinit var bookRepository: BookRepository

@field:Autowired
lateinit var r2dbcCustomConversions: R2dbcCustomConversions

@Test
fun testRepositoryJoin() {
    val source = bookRepository.bookAndAuthorById(1L)
        .log()

    // println(r2dbcCustomConversions)

    StepVerifier.create(source)
        .expectNextMatches { // assert author is not null
            it.author != null
        }
        .verifyComplete()
}

the output as follows,

...
R2DbcConfig.r2dbcCustomConversions()
2024-01-06T09:57:26.609+08:00  INFO 3416 --- [    Test worker] com.origami.r2dbc.SpringDataR2dbcTest    : Started SpringDataR2dbcTest in 3.28 seconds (process running for 5.131)
...
2024-01-06T09:57:27.780+08:00  INFO 3416 --- [    Test worker] reactor.Mono.Next.1                      : onSubscribe(MonoNext.NextSubscriber)
2024-01-06T09:57:27.783+08:00  INFO 3416 --- [    Test worker] reactor.Mono.Next.1                      : request(unbounded)
2024-01-06T09:57:29.124+08:00  INFO 3416 --- [actor-tcp-nio-2] reactor.Mono.Next.1                      : onNext(Book(id=1, title=Origami, authorId=1, publishTime=2024-01-05T14:13:22Z, author=null))
2024-01-06T09:57:29.129+08:00  INFO 3416 --- [actor-tcp-nio-2] reactor.Mono.Next.1                      : cancel()
2024-01-06T09:57:29.130+08:00  INFO 3416 --- [actor-tcp-nio-2] reactor.Mono.Next.1                      : onComplete()

We can see that the output only contains "R2DbcConfig.r2dbcCustomConversions()" but not "BookConverter.convert()".

And I used the IDE's debug to view the results of "r2dbcCustomConversions" and found "BookConveter" in it, but it did not take effect. (this -> r2dbcCustomConversions -> converters)

I saw it in the last one but it didn't work, how to fix this.

the build.gradle.kts:

plugins {
    id("org.springframework.boot") version "3.2.1"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("jvm") version "1.9.21"
    kotlin("plugin.spring") version "1.9.21"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    runtimeOnly("com.mysql:mysql-connector-j")
    runtimeOnly("io.asyncer:r2dbc-mysql")
    testImplementation("io.asyncer:r2dbc-mysql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("io.projectreactor:reactor-test")
}

I wanted to know why the converter didn't work. I also tried changing Kotlin to Java code, but it didn't work either.

1

There are 1 best solutions below

0
Origami Tobiichi On

I may have found the solution, I tried changing the Spring Boot version to 3.1.6 or 3.1.7 and it worked, but version 3.2.x does't worked

plugins {
    id("org.springframework.boot") version "3.1.7"
    // ...
}