【发布时间】:2026-01-05 10:35:01
【问题描述】:
我正在为使用 R2dbc 的项目添加带有 TestContainers 框架的 RepositoryTests,我遇到了以下情况:
1 - 在主项目中,我在 application.yaml 文件中设置了 r2dbc url(带有端口和主机名),spring 数据管理一切,一切正常。
2 - 然而,在测试中,我使用的是 TestContainers 框架,更具体地说是 DockerComposeContainer,我用它来使用 docker-compose.test.yaml 文件和我需要的数据库创建一个模拟容器。
3 - 这个容器在旅途中创建了一个port 编号我在我的 docker-compose 文件中定义了一个端口号,但是DockerComposeContainer 将提供给我的端口号是随机的,并且每次我运行测试时都会发生变化,这使得在application-test.yaml 上拥有静态网址不再是一个选项。
所以我需要在运行时动态地创建这个bean R2dbcEntityTemplate,并且只有在DockerComposeContainer 会给我端口号之后。所以我的应用程序可以连接到正确的端口,一切都应该按预期工作。
我尝试创建这个类:
package com.wayfair.samworkgroupsservice.adapter
import io.r2dbc.mssql.MssqlConnectionConfiguration
import io.r2dbc.mssql.MssqlConnectionFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Profile
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.data.r2dbc.dialect.SqlServerDialect
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.stereotype.Component
@Component
@Profile("test")
class TemplateFactory(
@Autowired val applicationContext: ApplicationContext
) {
private val beanFactory = applicationContext.autowireCapableBeanFactory as BeanDefinitionRegistry
fun registerTemplateBean(host: String, port: Int) {
val beanDefinition = GenericBeanDefinition()
beanDefinition.beanClass = R2dbcEntityTemplate::class.java
val args = ConstructorArgumentValues()
args.addIndexedArgumentValue(
0,
DatabaseClient.builder()
.connectionFactory(connectionFactory(host, port))
.bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
.build()
)
args.addIndexedArgumentValue(1, DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE))
beanDefinition.constructorArgumentValues = args
beanFactory.registerBeanDefinition("R2dbcEntityTemplate", beanDefinition)
}
// fun entityTemplate(host: String = "localhost", port: Int = 1435) =
// R2dbcEntityTemplate(
// DatabaseClient.builder()
// .connectionFactory(connectionFactory(host, port))
// .bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
// .build(),
// DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE)
// )
private fun connectionFactory(host: String, port: Int) =
MssqlConnectionFactory(
MssqlConnectionConfiguration.builder()
.host(host)
.port(port)
.username("sa")
.password("Password123@#?")
.build()
)
}
这就是我的数据库启动器的样子:
package com.wayfair.samworkgroupsservice.adapter.note
import com.wayfair.samworkgroupsservice.adapter.DBInitializerInterface
import com.wayfair.samworkgroupsservice.adapter.TemplateFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File
@Testcontainers
class NoteTagDBInitializer : DBInitializerInterface {
@Autowired
override lateinit var client: R2dbcEntityTemplate
@Autowired
lateinit var factory: TemplateFactory
override val sqlScripts = listOf(
"db/note/schema.sql",
"db/note/reset.sql",
"db/note/data.sql"
)
init {
factory.registerTemplateBean(
cont.getServiceHost("test-db-local_1", 1433),
cont.getServicePort("test-db-local_1", 1433)
)
}
companion object {
@Container
val cont: KDockerComposerContainer = KDockerComposerContainer("docker-compose.test.yml")
.withExposedService(
"test-db-local_1", 1433,
Wait.forListeningPort()
)
.withLocalCompose(true)
.also {
it.start()
val porttt = it.getServicePort("test-db-local_1", 1433)
print(porttt)
}
class KDockerComposerContainer(yamlFile: String) :
DockerComposeContainer<KDockerComposerContainer>(File(yamlFile))
}
}
尝试启动此模板工厂时没有收到任何有用的错误消息, 但老实说,我不知道我是否正在努力寻找正确的解决方案,有没有人知道如何解决这个问题,或者我在这里做错了什么?
所以总结一下生产应用程序很好,它从 application.yaml 文件上的 url 开始,就是这样,但对于测试,我需要一些动态的端口,每次都会改变。
提前谢谢你))
【问题讨论】:
标签: kotlin javabeans applicationcontext testcontainers