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.
I may have found the solution, I tried changing the Spring Boot version to
3.1.6or3.1.7and it worked, but version3.2.xdoes't worked