Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot error when using AbstractRoutingDataSource and trying to set up multiple DataSources

I'm pretty new to Spring boot and I'm working on a new app that needs to be able to connect to one of multiple available databases. Based on a user's credentials I'll determine what database to connect to so I need the ability to change connections dynamically at runtime. I found an old Spring blog that outlined a solution for this here that advocates using AbstractRoutingDataSource that routes getConnection() calls to other DataSources based on lookup keys. I tried to follow the blog carefully but I keep getting the following error.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': 
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'dataSource' defined in robb.referencecomponent.Application: 
Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: 
Failed to look up JNDI DataSource with name 'dev1DataSource'; nested exception is javax.naming.NoInitialContextException: 
Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:  java.naming.factory.initial

And here is the code for my Application.java class:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
}

    @Bean
    @ConfigurationProperties(prefix="app.dev1.datasource")
    public DataSource dev1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix="app.dev2.datasource")
    public DataSource dev2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource dataSource() {
        return new RoutingDataSource();
    }

    public class RoutingDataSource extends AbstractRoutingDataSource {
        public RoutingDataSource() {
            super();
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DbLocation.DEV1, "dev1DataSource");
            targetDataSources.put(DbLocation.DEV2, "dev2DataSource");
            setTargetDataSources(targetDataSources);
            setDefaultTargetDataSource(DbLocation.DEV2);
        }

        @Override
        protected Object determineCurrentLookupKey() {
           return ClientContextHolder.getDbLocation();
        }
    }

}

And I set the DataSource configurations in a properties file like so:

app.dev1.datasource.url=jdbc:oracle:thin:@dev1_db_url
app.dev1.datasource.username=dev1
app.dev1.datasource.password=XXXXXXX
app.dev1.datasource.driver-class-name=oracle.jdbc.OracleDriver

app.dev2.datasource.url=jdbc:oracle:thin:@dev2_db_url
app.dev2.datasource.username=dev2
app.dev2.datasource.password=XXXXXXX
app.dev2.datasource.driver-class-name=oracle.jdbc.OracleDriver

I named one of my DataSources 'dev1DataSource' and it's complaining that it can't look it up using JNDI, but when I remove the RoutingDataSource class and the dataSource() bean definition and make the dev1DataSource() bean @Primary I'm able to connect to the dev1 database fine. I'm not sure what I did wrong in porting over that older blog's code into my application, I know the beans were set up in the examples using xml but mine are set up with Java code and annotations, maybe some mistakes were made in that translation?

Does anyone have experience working with AbstractRoutingDataSource in Spring boot and have they had this sort of problem come up?

like image 978
Mr. Obb Avatar asked Oct 18 '22 13:10

Mr. Obb


1 Answers

AbstractRoutingDataSource supports multiple lookup mechanisms. The value type of the targetDataSources may vary, depending on the DataSourceLookup which defaults to JNDI lookup. That's why you see a NoInitialContextException on initialization.

You have two options how to fix your issue:

  1. Provide resolved DataSource instances instead of the dev1DataSource/dev2DataSource Strings. You define two DataSource @Bean methods, and you pass the DataSources to the initialization of your RoutingDataSource.
  2. Create an own DataSourceLookup that picks up the configuration properties from Environment. The own DataSourceLookup would have to take care of caching the created instances and application shutdown that's why I'd recommend option 1.
like image 71
mp911de Avatar answered Oct 21 '22 01:10

mp911de