Skip to content

BeanCurrentlyInCreationException when two DataSources used with DataSourceInitializer #9394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zachmarshall opened this issue Jun 2, 2017 · 15 comments
Assignees
Labels
status: duplicate A duplicate of another issue

Comments

@zachmarshall
Copy link

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 9c73312. However, 9c73312 only seems to update the Rabbit and JMS configuration. Should @ConditionalOnSingleCandidate also be used for the DataSourceInitializer?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 2, 2017
@snicoll
Copy link
Member

snicoll commented Jun 2, 2017

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.

@zachmarshall
Copy link
Author

zachmarshall commented Jun 2, 2017

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?

@snicoll snicoll added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 11, 2017
@snicoll snicoll added this to the 1.5.5 milestone Jun 11, 2017
@snicoll snicoll self-assigned this Jun 11, 2017
@snicoll
Copy link
Member

snicoll commented Jun 15, 2017

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.

@zachmarshall
Copy link
Author

@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.

@snicoll
Copy link
Member

snicoll commented Jun 15, 2017

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.

@zachmarshall
Copy link
Author

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

@snicoll snicoll modified the milestones: 2.0.0.M3, 2.0.0.M4 Jul 13, 2017
@wilkinsona wilkinsona modified the milestones: 2.0.0.M5, 2.0.0.M4 Jul 28, 2017
@snicoll
Copy link
Member

snicoll commented Oct 3, 2017

Duplicate of #9528

@snicoll snicoll marked this as a duplicate of #9528 Oct 3, 2017
@snicoll snicoll closed this as completed Oct 3, 2017
@snicoll snicoll added status: duplicate A duplicate of another issue and removed priority: normal type: bug A general bug labels Oct 3, 2017
@rvit34
Copy link

rvit34 commented Jun 20, 2018

@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

@awesomekosm
Copy link

awesomekosm commented Apr 29, 2019

@rvit34 Can probably wrap the DataSource

class DSHolder {
    DataSource dataSource
}

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

@AleksandarTokarev
Copy link

AleksandarTokarev commented Jan 10, 2020

@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?

@rvit34
Copy link

rvit34 commented Jan 13, 2020

@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()
}

@wuyupengwoaini
Copy link

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

@msangel

This comment has been minimized.

@snicoll

This comment has been minimized.

@Xlong99
Copy link

Xlong99 commented Aug 25, 2021

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;
}`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests