You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot+PostgreSQL迁移遇Flyway循环依赖Bean创建错误求助

问题:Spring Boot + Flyway 数据迁移时的循环依赖异常

我开发了一个基于Spring Boot Data JPA和PostgreSQL的应用,需要执行数据迁移:从数据库读取数据、生成序列值、赋值给实体后保存回库。逻辑本身不复杂,但遇到了如下异常:

org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'jdbcSequenceGenerator' defined in class path resource
[com/mydocs/configuration/SequenceGeneratorConfiguration.class]:
Unsatisfied dependency expressed through method 'jdbcSequenceGenerator' parameter 0;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'flyway': Requested bean is currently in creation:
Is there an unresolvable circular reference?

推测问题出在同时使用数据库数据流和生成序列的操作,但两者都是迁移必需的,求解决方法。


相关代码

序列生成器配置类 com/mydocs/configuration/SequenceGeneratorConfiguration.class

@Configuration
class SequenceGeneratorConfiguration {
    @Bean
    fun jdbcSequenceGenerator(jdbcTemplate: JdbcTemplate, manager: PlatformTransactionManager): JdbcSequenceGenerator {
        val transactionTemplate = TransactionTemplate(manager)
        transactionTemplate.isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
        transactionTemplate.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
        return JdbcSequenceGenerator(jdbcTemplate, transactionTemplate)
    }
}

Flyway迁移类

@Component
class V1689943918__Migrate_Documents(
    @Lazy private val messageRepository: MessageEntityRepository,
    private val mapper: EntityMapper,
) : BaseJavaMigration() {

    override fun migrate(context: Context) {
        messageRepository.findAllByOrderByCreatedAt().use { entities: Stream<MessageEntity> ->
            entities.forEach { migrate(it) }
        }
    }

    private fun migrate(entity: MessageEntity) {
        entity.data.attachments
            ?.map { attachmentEntity ->
                val number = generateDocumentSequence(entity.chatId.toString())
                mapper.mapToDocumentEntity(entity.chatId, number, attachmentEntity)
            }
            ?.let { documentEntities ->
                messageRepository.save(entity.copy(documents = documentEntities))
            }
    }
}

序列生成器实现类

class JdbcSequenceGenerator(
    private val jdbcTemplate: JdbcTemplate,
    private val transactionTemplate: TransactionTemplate
) : SequenceGenerator {

    override fun generateSequence(vararg parts: String?): Long {
        return transactionTemplate.execute {
            jdbcTemplate.queryForObject(UPSERT_SQL, Long::class.java, parts.joinToString(separator = "_"), INITIAL_VALUE)
        } ?: throw RuntimeException("Sequence generation error")
    }

    companion object {
        const val DOCUMENT_SEQUENCE_NAME = "document_sequence"
        private const val INITIAL_VALUE = 1L

        private const val UPSERT_SQL = """
            INSERT INTO _generated_sequences AS t (id, sequence) 
            VALUES (?, ?) 
            ON CONFLICT (id) DO UPDATE 
            SET sequence = t.sequence + 1 
            RETURNING sequence
        """
    }
}

解决方案

核心原因

Flyway启动时会优先初始化并执行迁移脚本,而你的JdbcSequenceGenerator依赖的JdbcTemplate、PlatformTransactionManager,与Flyway的初始化流程形成循环依赖:Flyway需要依赖数据源,你的Bean又依赖数据源相关组件,同时迁移类依赖序列生成器,导致Spring创建Bean时出现循环引用。

具体解决方法

方法1:给序列生成器Bean添加延迟初始化

修改配置类,给jdbcSequenceGenerator方法加上@Lazy,让Spring在真正用到这个Bean时才创建,避免和Flyway启动初始化冲突:

@Configuration
class SequenceGeneratorConfiguration {
    @Bean
    @Lazy
    fun jdbcSequenceGenerator(jdbcTemplate: JdbcTemplate, manager: PlatformTransactionManager): JdbcSequenceGenerator {
        val transactionTemplate = TransactionTemplate(manager)
        transactionTemplate.isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
        transactionTemplate.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
        return JdbcSequenceGenerator(jdbcTemplate, transactionTemplate)
    }
}

方法2:在迁移类中通过Flyway上下文直接创建序列生成器

既然迁移类继承了BaseJavaMigration,可以直接从Context获取数据库连接,不需要依赖Spring容器中的Bean,彻底规避循环依赖:

@Component
class V1689943918__Migrate_Documents(
    @Lazy private val messageRepository: MessageEntityRepository,
    private val mapper: EntityMapper,
) : BaseJavaMigration() {

    override fun migrate(context: Context) {
        // 从Flyway上下文获取数据源创建JdbcTemplate
        val jdbcTemplate = JdbcTemplate(context.configuration.dataSource)
        val sequenceGenerator = JdbcSequenceGenerator(jdbcTemplate, createTransactionTemplate(context))

        messageRepository.findAllByOrderByCreatedAt().use { entities: Stream<MessageEntity> ->
            entities.forEach { migrate(it, sequenceGenerator) }
        }
    }

    private fun createTransactionTemplate(context: Context): TransactionTemplate {
        val transactionManager = DataSourceTransactionManager(context.configuration.dataSource)
        val transactionTemplate = TransactionTemplate(transactionManager)
        transactionTemplate.isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
        transactionTemplate.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
        return transactionTemplate
    }

    private fun migrate(entity: MessageEntity, sequenceGenerator: JdbcSequenceGenerator) {
        entity.data.attachments
            ?.map { attachmentEntity ->
                val number = sequenceGenerator.generateSequence(entity.chatId.toString())
                mapper.mapToDocumentEntity(entity.chatId, number, attachmentEntity)
            }
            ?.let { documentEntities ->
                messageRepository.save(entity.copy(documents = documentEntities))
            }
    }
}

方法3:调整Flyway初始化顺序(可选)

在配置文件中设置Flyway在Spring Data JPA之后初始化,仅适用于不需要Flyway执行早期schema迁移的场景:

spring.flyway.order=1000

内容的提问来源于stack exchange,提问作者NeverSleeps

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 14:50:36