Spring Boot+PostgreSQL迁移遇Flyway循环依赖Bean创建错误求助
我开发了一个基于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

