Я попытался обновить один из моих примеров проектов до последней версии стека Spring Boot 3.0.5, исходные тесты Spring Data R2dbc не увенчались успехом.
Объект R2dbc выглядит следующим образом.
@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,
)
PostSummary
и PostRepository
это:
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>
}
Тесты репозитория такие.
@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")
}
...
}
Я получил следующие исключения:
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
Я не уверен, что он пытается создать объект при обновлении существующего объекта? В тесте я просто хочу обновить заголовок/статус, а не обновить содержимое, что вызвало это исключение.
Обновление: измените
content
на nullable, тест пройден. Но я считаю это неразумным.
Окончательный выбор вызвал проблему, которая не выбирает поле content
. Хотя результатом запроса является Flow<PostSummary>
, запрос по-прежнему пытается сначала создать экземпляр Post
.
Ваш анализ неверен. Оператор обновления проходит, и оператор позже SELECT posts.id, posts.title FROM posts
не выбирает столбец content
; Следовательно, вы не можете создать экземпляр объекта.
Да, ты прав.
findByTitleContains
возвращаетFlow<PostSummary>
, ноPostSummary
не включает полеcontent
. Почему отображение результатов запроса здесь проблематично? Он сначала создалPost
(но использовал поля вPostSummary
), а затем преобразовал вPostSummary
?