16

I am developing a Spring Boot based application in which I would like to create 2 beans: One will point to 'Oracle' database; the other will point to Hive. I've declared them as follows:

public @Bean
BoneCPDataSource metadataDataSource() {
    BoneCPDataSource boneCPDataSource = new BoneCPDataSource();
    boneCPDataSource.setDriverClass(getDriver());
    boneCPDataSource.setJdbcUrl(getJdbcUrl());
    boneCPDataSource.setUser(getUser());
    boneCPDataSource.setPassword(getPassword());
    boneCPDataSource.setMaxConnectionsPerPartition(5);
    boneCPDataSource.setPartitionCount(5);
    return boneCPDataSource;
}

public @Bean
BasicDataSource hiveDataSource() {
    BasicDataSource basicDataSource = new BasicDataSource();

    // Note: In a separate command window, use port forwarding like this:
    //
    // ssh -L 127.0.0.1:9996:<server>:<port> -l <userid> <server>
    //
    // and then login as the generic user.

    basicDataSource.setDriverClassName("org.apache.hadoop.hive.jdbc.HiveDriver");
    basicDataSource.setUrl("jdbc:hive://127.0.0.1:9996:10000/mytable");
    return basicDataSource;
}

Problem is at the startup I am getting this:

Exception in thread "main"
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name
'org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration':
Injection of autowired dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: private javax.sql.DataSource
org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration.dataSource;
nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type [javax.sql.DataSource] is defined: expected
single matching bean but found 2: metadataDataSource,hiveDataSource

Mainly because both of them inherit from javax.sql.DataSource. What's the best way to fix this?


EDIT:

Now I've declared them as follows:

public @Bean (name="metadataDataSource")
BoneCPDataSource metadataDataSource() {
    BoneCPDataSource boneCPDataSource = new BoneCPDataSource();
    boneCPDataSource.setDriverClass(getDriver());
    boneCPDataSource.setJdbcUrl(getJdbcUrl());
    boneCPDataSource.setUser(getUser());
    boneCPDataSource.setPassword(getPassword());
    boneCPDataSource.setMaxConnectionsPerPartition(5);
    boneCPDataSource.setPartitionCount(5);
    return boneCPDataSource;
}

public @Bean (name="hiveDataSource")
BasicDataSource hiveDataSource() {
    BasicDataSource basicDataSource = new BasicDataSource();

    // Note: In a separate command window, use port forwarding like this:
    //
    // ssh -L 127.0.0.1:9996:<server>:<port> -l <userid> <server>
    //
    // and then login as the generic user.

    basicDataSource.setDriverClassName("org.apache.hadoop.hive.jdbc.HiveDriver");
    basicDataSource.setUrl("jdbc:hive://127.0.0.1:9996:10000/mytable");
    return basicDataSource;
}

And got this exception:

Exception in thread "main"
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name
'org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration':
Injection of autowired dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: private javax.sql.DataSource
org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration.dataSource;
nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type [javax.sql.DataSource] is defined: expected
single matching bean but found 2: metadataDataSource,hiveDataSource

The other classes are referring to these beans as follows:

public class MetadataProcessorImpl implements MetadataProcessor {

@Autowired
@Qualifier("metadataDataSource")
BoneCPDataSource metadataDataSource;

@Controller public class HiveController {

@Autowired
@Qualifier("hiveDataSource")
BasicDataSource hiveDataSource;
DilTeam
  • 2,551
  • 9
  • 42
  • 69
  • Luiggi - I am not sure what you're suggesting. Was my question not appropriate? I googled for answers before asking this, but the results I got didn't really help. But I must confess I didn't spend a lot of time going thru all of them. Is that what I am expected to do? – DilTeam May 15 '14 at 20:26
  • I mean that you should accept the most useful post in your question as the answer. Sorry if my words didn't express that. – Luiggi Mendoza May 15 '14 at 20:27
  • Let me rephrase my comment: You should [accept the post that helped you most as the answer](http://meta.stackexchange.com/a/118694/182862) in your questions. After reviewing your profile, you have accepted none, so I recommend you to take some time to accept the bests answers (if any). There's a link in the comment that explains how this works. – Luiggi Mendoza May 15 '14 at 20:35
  • Thanks for the explanation. I didn't know about the 'accept' feature. Will definitely use it going forward. – DilTeam May 15 '14 at 20:40
  • The problem is in this class: `org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration`. Can you see if you define this class or a subclass in XML or in another class within your project? – Luiggi Mendoza May 15 '14 at 21:09
  • EndpointAutoConfigurtion is part of 'Spring Boot'. It has the following: @Autowired(required = false) private DataSource dataSource; This is the one that's causing the issue. This works until I add the 2nd datasource ('hiveDataSource'). – DilTeam May 15 '14 at 21:50
  • Then you have few options. Or you change the version of spring boot where this class doesn't contain that field or remove spring boot at all, another option may be removing one of your data sources but I don't think you want that. – Luiggi Mendoza May 15 '14 at 21:53
  • Not using Spring Boot is not an option. We're fairly committed to it. Seems like it doesn't allow us to use more than one 'Data Source'. That's sad :( To get around it, I guess I can create a Singleton HiveDataSource for now. – DilTeam May 15 '14 at 23:06
  • You know, I read the [source](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java) of `EndpointAutoConfiguration` and there's no such class. Are you sure you're using the right version of spring boot? – Luiggi Mendoza May 15 '14 at 23:28
  • There's no such class? I debugged thru it. It's located here: \.m2\repository\org\springframework\boot\spring-boot-actuator\1.0.1.RELEASE\spring-boot-actuator-1.0.1.RELEASE-sources.jar!\org\springframework\boot\actuate\autoconfigure\EndpointAutoConfiguration.java – DilTeam May 15 '14 at 23:43
  • Sorry, I wrote it fast. It was field, not class. – Luiggi Mendoza May 15 '14 at 23:53
  • After upgrading spring boot to 1.1.0.BUILD-SNAPSHOT version, started getting same error from: DataSourceAutoConfiguration. Seems like the code got moved around, but essentially we still can't use more than one 'Datasources' in Spring Boot - it appears! Error creating bean with name.... org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: metadataDataSource,hiveDataSource – DilTeam May 16 '14 at 19:48
  • Well, looks like you have to talk to the software architecture team or the technical leader or the person that takes the technical decisions and explain this problem in detail to take the final decision on this after analyzing the impacts of the changes on the application design. – Luiggi Mendoza May 16 '14 at 19:49

4 Answers4

19

Set one of the beam as @Primary as described in the section 67.2 Configure Two DataSources

http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-two-datasources

Community
  • 1
  • 1
Danilo
  • 201
  • 2
  • 3
  • the Primary was exactly my problem because of the : "default auto-configuration for JDBC or JPA " , thanks ! . – Robocide Sep 02 '15 at 08:15
  • xml equivalent for @Primary: https://stackoverflow.com/questions/45548471/spring-xml-equivalent-of-primary/45548585#45548585 – faghani May 12 '18 at 13:39
  • This is the right answer, but I still don't understand when will this "@Primary" be used. Let's say you use only the non primary datasource calling it with a @Qualifier, why would u need the other one to be annotated "@Primary" ? Or is it just a buggy autoconf code whci checks how many objects of Datasource type are present without checking the bean names ? – Tristan Nov 05 '18 at 11:52
12

Give different names to your beans when using @Bean:

@Bean(name="bonecpDS")
public BoneCPDataSource metadataDataSource() {
    //...
}

@Bean(name="hiveDS")
public BasicDataSource hiveDataSource() {
    //...
}

Then, when injecting the bean, use @Qualifier and specify the name of the bean:

@Component
public class FooComponent {
    @Autowired
    @Qualifier("bonecpDS")
    DataSource boneCPDataSource;
}
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • 1
    It's not working for me. Added this: public @Bean (name="metadataDataSource") BoneCPDataSource metadataDataSource() { – DilTeam May 15 '14 at 20:36
  • @androidfan please do not post code in comments. Instead, edit your question and add the changes you've done at the bottom, also please don't remove the current content of the question. – Luiggi Mendoza May 15 '14 at 20:37
  • @androidfan please post the code where you're attempting to inject the bean, looks like you haven't used the `@Qualifier` annotation. – Luiggi Mendoza May 15 '14 at 20:43
  • Let's just say for now that I am not using them. Shouldn't the application start without any errors? – DilTeam May 15 '14 at 20:54
  • @androidfan this class: `org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration` is using it. Haven't you declare this class or a subclass somewhere else in your code or in XML configuration? – Luiggi Mendoza May 15 '14 at 20:57
  • 1
    Sometimes you may need to assign datasource, transactionManager, and SqlSessionFactory as primary all. – Dai Kaixian Dec 15 '16 at 10:38
  • One of the Datasource should be given as @Primary – Karthik Nagaraj Aug 22 '17 at 12:23
4

If you want to use two data sources at the same time and they are not primary and secondary, you should disable DataSourceAutoConfiguration on your application annotated by @SpringBootApplication(excludes = {DataSourceAutoConfiguration.class}).

Since the DataSourceAutoConfiguration will init the DataSourceInitializer class. The init method in DataSourceInitializer class needs to get DataSource. When there is more than one DataSource, the system gets confused by getting which DataSource.

@SpringBootApplication(excludes = {DataSourceAutoConfiguration.class}) means that system won't load the DataSourceAutoConfiguration.class when run the application.

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
Tina Zhang
  • 41
  • 2
  • 1
    Works like a charm!! I was looking for a solution for the problem for 2 days! I only have something to comment, it is misspelled "excludes", this should be: "exclude". Thanks a lot!!! – jorghe94 Nov 26 '19 at 16:18
0

I faced similar issue. Added @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class})

and added manual configuration. It worked!

Mahesh Vemula
  • 145
  • 2
  • 14