I tried to update one of my example project to the latest Spring Boot 3.0.5 stack, the original Spring Data R2dbc tests failed.
The R2dbc entity is like this.
@Table(value = "posts")
data class Post(
@Id
@Column("id")
val id: UUID? = null,
@Column("title")
var title: String,
@Column("content")
var content: String,
@Column("status")
var status: Status = Status.DRAFT,
@Column("created_at")
@CreatedDate
val createdAt: LocalDateTime? = null,
@Column("created_by")
@CreatedBy
val createdBy: String? = null,
@Column("updated_at")
@LastModifiedDate
val updatedAt: LocalDateTime? = null,
@Column("version")
@Version
@JsonIgnore
val version: Long? = null,
)
The PostSummary
and PostRepository
is:
data class PostSummary(var id: UUID, var title: String)
interface PostRepository :
CoroutineCrudRepository<Post, UUID>, CoroutineSortingRepository<Post, UUID> {
fun findByTitleContains(title: String): Flow<PostSummary>
fun findByStatus(status: Status): Flow<Post>
}
The repository tests is like this.
@OptIn(ExperimentalCoroutinesApi::class)
@DataR2dbcTest
@Testcontainers
@Import(DataConfig::class)
class PostRepositoryTest {
companion object {
private val log = LoggerFactory.getLogger(PostRepositoryTest::class.java)
@Container
private val postgreSQLContainer: PostgreSQLContainer<*> = PostgreSQLContainer<Nothing>("postgres:12")
@DynamicPropertySource
@JvmStatic
fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.r2dbc.url") {
"r2dbc:postgresql://${postgreSQLContainer.host}:${postgreSQLContainer.firstMappedPort}/${postgreSQLContainer.databaseName}"
}
registry.add("spring.r2dbc.username") { postgreSQLContainer.username }
registry.add("spring.r2dbc.password") { postgreSQLContainer.password }
}
}
@Autowired
lateinit var dbclient: DatabaseClient
@Autowired
lateinit var template: R2dbcEntityTemplate
@Autowired
lateinit var posts: PostRepository
@BeforeEach
fun setup() = runTest {
val deleted = template.delete(Post::class.java).all().awaitSingle()
log.debug("clean posts list before tests: $deleted")
}
@Test
fun testDatabaseClientExisted() {
assertNotNull(dbclient)
}
@Test
fun testR2dbcEntityTemplateExisted() {
assertNotNull(template)
}
@Test
fun testPostRepositoryExisted() {
assertNotNull(posts)
}
@Test
fun testInsertAndQuery() = runTest {
val data = Post(title = "test title", content = "test content")
val saved = posts.save(data)
// verify id is inserted.
assertNotNull(saved.id)
val existed = posts.findById(saved.id!!)!!
log.debug("found existed post: $existed")
//verify the saved data
assertThat(existed.title).isEqualTo("test title")
assertThat(existed.status).isEqualTo(Status.DRAFT)
existed.apply {
title = "update title"
status = Status.PENDING_MODERATION
}
posts.save(existed) // this line caused failure
val updatedPosts = posts.findByTitleContains("update")
//verify the updated title
assertThat(updatedPosts.count()).isEqualTo(1)
assertThat(updatedPosts.toList()[0].title).isEqualTo("update title")
}
...
}
I got the following exceptions:
2023-04-15T12:18:33.376+08:00 DEBUG 8320 --- [in @coroutine#2] com.example.demo.PostRepositoryTest :
found existed post: Post(id=1ba70b74-8de6-4200-8953-40f180a08c8b, title=test title, content=test content, status=DRAFT, createdAt=2023-04-15T12:18:33.213805, createdBy=null, updatedAt=2023-04-15T12:18:33.213805, version=0)
2023-04-15T12:18:33.528+08:00 DEBUG 8320 --- [in @coroutine#2] o.s.r2dbc.core.DefaultDatabaseClient :
Executing SQL statement [UPDATE posts SET title = $1, content = $2, status = $3, created_at = $4, created_by = $5, updated_at = $6, version = $7 WHERE posts.id = $8 AND (posts.version = $9)]
2023-04-15T12:18:33.607+08:00 DEBUG 8320 --- [in @coroutine#2] o.s.r2dbc.core.DefaultDatabaseClient :
Executing SQL statement [SELECT posts.id, posts.title FROM posts WHERE posts.title LIKE $1]
org.springframework.data.mapping.model.MappingInstantiationException:
Failed to instantiate com.example.demo.domain.model.Post
using constructor fun <init>(java.util.UUID?, kotlin.String, kotlin.String, com.example.demo.domain.model.Status, java.time.LocalDateTime?, kotlin.String?, java.time.LocalDateTime?, kotlin.Long?):
com.example.demo.domain.model.Post with arguments
1ba70b74-8de6-4200-8953-40f180a08c8b,update title,null,null,null,null,null,null,248,null
at org.springframework.data.mapping.model.
KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance
I am not sure it is trying to instantiate an entity when updating the existed entity? In the test, I just want to update the title/status, not update content, which caused this exception.
Update: change
content
to nullable, the test is passed. But I think it is unreasonable.
The final select caused the issue, which does not select the content
field. Although the query result is a Flow<PostSummary>
, but the query still try to instantiate a Post
firstly.
Your analysis isn't correct. The update statement passes, and the statement later on SELECT posts.id, posts.title FROM posts
doesn't select the content
column; Therefore, you cannot instantiate the object.