It appears that when there are two DataSource beans created and one bean factory method calls the other DataSource's bean factory method (e.g. in a delegate pattern), DataSourceInitializer triggers a circular bean reference. It doesn't help that one bean is tagged @Primary.

Apologies if I'm doing something incorrectly. I posted at Stack Overflow and did not get responses. I reviewed the doc and I believe I've followed that, and it does not mention this delegate pattern.

I've created a sample repository with two junit tests that demonstrate the issue. The full stack trace is also available there along with some analysis. In summary, the @Primary DataSource bean triggers creation of the subordinate DataSource bean which completes successfully. The creation of the subordinate bean triggers DataSourceInitializer which asks the bean factory for the DataSource and the bean factory selects the @Primary which is still in creation.

I've reviewed both open and closed issues and found several relevant issues: #2383 is similar but isn't using @Primary, #8068 seems unrelated, #7652 mentions tricks needed for a similar setup but doesn't have the same relationship between the two beans, #5541 implies this should work but again doesn't have one factory bean calling the other factory bean, #5104 suggests using @Primary which doesn't help in this case. #2784 seems to be the closest match and was closed per 9c733128ac942ac4675b27d2d358afa941fea5fd. However, 9c733128ac942ac4675b27d2d358afa941fea5fd only seems to update the Rabbit and JMS configuration. Should @ConditionalOnSingleCandidate also be used for the DataSourceInitializer?

Comment From: snicoll

Should @ConditionalOnSingleCandidate also be used for the DataSourceInitializer?

We intend to do that, yes see #6560. There could be a way to break that cycle though.

Comment From: zachmarshall

Thanks and sorry I missed that issue in my search. I see you've asked to keep convo here and will do so.

I do agree that I would like to resolve this without requiring that approach. Why does DataSourceInitializer even exist as a bean? Why not do the actual work in DataSourceInitializerPostProcessor on either each and every DataSource or only on @Primary beans?

Comment From: snicoll

Ironically enough, that test passes in IntelliJ IDEA.

I can see where the problem comes from now. The BPP attempts to fully initialize DataSourceInitializer as soon as it sees a DataSource. The problem in your example is that the secondary one (that's not primary) is the first one to be post-processed so it early triggers the initialization of the DataSourceInitalizer and that has the effect of requesting the primary DataSource to be resolved.

I am not sure I get why IJ does not reveal the cycle. But I think I have an idea to fix it.

Comment From: zachmarshall

@snicoll thanks, that was my analysis as well. I did find a workaround - by using @DependsOn("dataSourceInitializer") on the @Primary bean definition, spring eagerly creates the dataSourceInitializer bean before the primary is marked as in creation. Kind of a hack but works for now.

Comment From: snicoll

Given you have a workaround and changing things in the BPP may break other use cases, I am tempted to close this issue as a duplicate of #9528. Let see what the rest of the team think.

Comment From: zachmarshall

Yes, I think a DUP of #9528 makes sense. Thanks for looking and opening that issue.

Comment From: snicoll

Duplicate of #9528

Comment From: rvit34

@zachmarshall @snicoll it seems that your workaround does not work in Spring Boot 2.0 app with two secondary data sources and one primary RoutingDataSource.

This is my config:

@Configuration
//@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MultiDataSourceConfig {

    @Bean
    @DependsOn("dataSourceInitializer")
    @Primary
    public DataSource routingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("write", masterDataSource);
        dataSourceMap.put("read", slaveDataSource);
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);

        return routingDataSource;
    }

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "db.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "db.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

ReplicationRoutingDataSource:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "read" : "write";
    }
}

What I get is:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'dataSourceInitializer' available

When I tried to exclude some auto configurations and commented out @DependsOn("dataSourceInitializer") I get the issue from title:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'routingDataSource' defined in class path resource [MultiDataSourceConfig.class]: Unsatisfied dependency expressed through method 'routingDataSource' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'masterDataSource' defined in class path resource [MultiDataSourceConfig.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'routingDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

Any thoughts how to fix it?

Spring Boot 2.0.0.RELEASE Spring Data JPA 2.0.5.RELEASE

Comment From: awesomekosm

@rvit34 Can probably wrap the DataSource

class DSHolder {
    DataSource dataSource
}

ConfigurationProperties probably won't work so have to do it old way.

Comment From: AleksandarTokarev

@zachmarshall

I don't have such bean @DependsOn("dataSourceInitializer"). Can you provide an example so I can use this in my application?

I am getting this issue after upgrading to Spring Boot 2.1.3 from 1.5.X I am getting the same error as @rvit34

No bean named 'dataSourceInitializer' available

@rvit34 Did you find a solution?

Comment From: rvit34

@AleksandarTokarev I cleaned my code above and forget about solution. But I remember that it worked. Now I am using similar configuration with SB 2.1/2.2 and Kotlin. And It works:

@Configuration
class DBAutoConfiguration {

    @Bean("masterDataSourceProperties")
    @Primary
    @ConfigurationProperties("spring.datasource")
    fun masterDataSourceProperties() = DataSourceProperties()

    @Bean("slaveDataSourceProperties")
    @ConditionalOnProperty("spring.slave-datasource.enabled", havingValue = "true")
    @ConfigurationProperties("spring.slave-datasource")
    fun slaveDataSourceProperties() = DataSourceProperties()

    @Bean("masterDataSource")
    @ConfigurationProperties("spring.datasource")
    fun masterDataSource(@Qualifier("masterDataSourceProperties") masterProperties: DataSourceProperties) = masterProperties.initializeDataSourceBuilder().build()

    @Bean("slaveDataSource")
    @ConditionalOnProperty("spring.slave-datasource.enabled", havingValue = "true")
    @ConfigurationProperties("spring.slave-datasource")
    fun slaveDataSource(@Qualifier("slaveDataSourceProperties") slaveProperties: DataSourceProperties) = slaveProperties.initializeDataSourceBuilder().build()

    @Bean("routingDataSource")
    @DependsOn("masterDataSource")
    @Primary
    fun routingDataSource(
        @Qualifier("masterDataSource") masterDataSource: DataSource,
        @Qualifier("slaveDataSource") slaveDataSource: DataSource?
    ): DataSource {
        val dataSource = RoutingDataSource()
        dataSource.setTargetDataSources(mapOf(
            if (slaveDataSource != null) {
                DataSourceType.MASTER to masterDataSource
                DataSourceType.SLAVE to slaveDataSource
            } else {
                DataSourceType.MASTER to masterDataSource
            })
        )
        dataSource.setDefaultTargetDataSource(masterDataSource)
        return dataSource
    }

    @Bean
    @ConditionalOnProperty("spring.slave-datasource.enabled", havingValue = "true")
    fun dataSourceConnectionInterceptor() = DataSourceConnectionInterceptor()
}

class RoutingDataSource : AbstractRoutingDataSource() {
    override fun determineCurrentLookupKey(): DataSourceType = DataSourceTypeHolder.dataSource
}

enum class DataSourceType {
    MASTER, SLAVE
}

object DataSourceTypeHolder {

    private val typeHolder = ThreadLocal<DataSourceType>()

    val dataSource: DataSourceType
        get() = typeHolder.get() ?: DataSourceType.MASTER

    fun putDataSource(dataSourceType: DataSourceType) {
        typeHolder.set(dataSourceType)
    }

    fun clear() = typeHolder.remove()
}

Comment From: wuyupengwoaini

@AleksandarTokarev The easiest solution is to exclude DataSourceAutoConfiguration.class like the following: @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

Comment From: msangel

Can't say this bug is closed, as I need to use this initializer on the secondary datasource, that cannot be marked as @Primary

Can this be made as @ConditionalOnMissing with custom implementations (where the propper db will be referencing to).

Still open question when there will be more then 2 DBs and some of them will require this.

Comment From: snicoll

@msangel please review the history. This issue has been closed as a duplicate. If you follow the links you'll see that this has been reworked heavily for the upcoming Spring Boot 2.5 version.

Comment From: Xlong99

If the version you are using still has this issues and u use mybatis-starter,And need to configure multiple data source beans in Beanfactory i also find a work around ---by annotation @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class}) to exclude the autoconfiguration class of data source,and register datasource by @Bean yourself like this:

/**
 * DataSourceA
 *
 * @return
 */
@Bean(name = "DataSourceA", initMethod = "init")
public DataSource DataSourceA() {
    DataSource dataSourceA = new DataSource();
    //........
    return dataSourceA;
}

/**
 * DataSourceB
 *
 * @return
 */
@Bean(name = "DataSourceB", initMethod = "init")
public DataSource oldDataSource() {
    DataSource dataSourceB = new DataSource();
    //........
    return dataSourceB;
}

/**
 * DynamicDataSource
 *
 * @return
 */
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dynamicDataSource(@Qualifier("DataSourceA") DataSource DataSourceA,
                                           @Qualifier("DataSourceB") DataSource DataSourceB) {
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put(DataSourceEnum.NEW, DataSourceA);
    targetDataSources.put(DataSourceEnum.OLD, DataSourceB);
    DynamicDataSource dataSource = new DynamicDataSource();
    // AbstractRoutingDataSource
    dataSource.setTargetDataSources(targetDataSources);
    //
    dataSource.setDefaultTargetDataSource(DataSourceA);
    return dataSource;
}

/**
 * SqlSessionFactory
 *
 * @param dataSource
 * @return
 * @throws Exception
 */
@Bean
public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource)
        throws Exception {
    final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
    //.....
    sessionFactoryBean.setDataSource(dataSource);
    SqlSessionFactory sqlSessionFactory = sessionFactoryBean.getObject();
    return sqlSessionFactory;
}`