Paul
Paul

Reputation: 4530

NoSuchBeanDefinitionException with Multiple JdbcTemplate and Spring Boot

I am using spring-boot-starter-jdbc and trying to use multiple Jdbc DataSources, everything worked when I used named Beans for my JdbcTemplate and then used @Qualifier to inject the right JdbcTemplate into my repository.

Application:

package my.app;

@SpringBootApplication
@EnableAutoConfiguration
public class Application implements CommandLineRunner {

  @Autowired
  private MyRepository repository;

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

  @Override
  public void run(String... args) throws Exception {
    List<Stuff> stuff = repository.getStuff();
  }
}

Configuration:

package my.app;

@Configuration
@ComponentScan
public class AppConfig {
  @Bean(name = "datasource1")
  @ConfigurationProperties(prefix = "db1.datasource")
  public DataSource dataSource1() {
    return DataSourceBuilder.create().build();
  }

  @Bean(name = "db1")
  public DataSource db1(@Qualifier("datasource1" DataSource ds) {
    return new JdbcTemplate(ds);
  }

  @Bean(name = "datasource2")
  @ConfigurationProperties(prefix = "db2.datasource")
  public DataSource dataSource2() {
    return DataSourceBuilder.create().build();
  }

  @Bean(name = "db2")
  public DataSource db1(@Qualifier("datasource1" DataSource ds) {
    return new JdbcTemplate(ds);
  }

}

Repository:

package my.app;

@Repository
public class MyRepository {
  private JdbcTemplate db1;
  private JdbcTemplate db2;

  @Autowired
  public class MyRepository(@Qualifier("db1") JdbcTemplate db1, @Qualifier("db2") JdbcTemplate db2) {
    this.db1 = db1;
    this.db2 = db2;
  }
}

When I instantiate MyRepository everything is fine.

I did a little refactoring to try to make new classes for Db1JdbcTemplate and Db2JdbcTemplate so I could inject them without qualifiers. Unfortunately when I do this I get the following exception:

Here's what I attempted to do:

Removed named JdbcTemplate Beans from AppConfig:

package my.app;

@Configuration
@ComponentScan
public class AppConfig {
  @Bean(name = "datasource1")
  @ConfigurationProperties(prefix = "db1.datasource")
  public DataSource dataSource1() {
    return DataSourceBuilder.create().build();
  }

  @Bean(name = "datasource2")
  @ConfigurationProperties(prefix = "db2.datasource")
  public DataSource dataSource2() {
    return DataSourceBuilder.create().build();
  }
}

Created 2 new classes for named JdbcTemplates:

package my.app;

@Component
public class Db1JdbcTemplate extends JdbcTemplate {
  @Autowired
  public Db1JdbcTemplate(@Qualifier("datasource1") DataSource ds1) {
    super(ds1);
  }
} 
package my.app;

@Component
public class Db2JdbcTemplate extends JdbcTemplate {
  @Autowired
  public Db2JdbcTemplate(@Qualifier("datasource2") DataSource ds2) {
    super(ds2);
  }
} 

Modified MyRepository to use those:

package my.app;

@Repository
public class MyRepository {
  private Db1JdbcTemplate db1;
  private Db2JdbcTemplate db2;

  @Autowired
  public class MyRepository(Db1JdbcTemplate db1, Db2JdbcTemplate db2) {
    this.db1 = db1;
    this.db2 = db2;
  }
}

So I have @Component annotations on both of those so I expected to be able to be registered and be able to be Autowired. However when I run things I get the following exception:

Error creating bean with name 'repository' defined in file [/Users/me/spring/target/classes/my/app/MyRepository.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'my.app.Db1JdbcTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Any ideas what I need to change or how I can debug further? I can always go back to the working version but I'd prefer to be more explicit when possible so the compiler can help me out instead of runtime errors caused by a typo'd Qualifier name.

Upvotes: 0

Views: 580

Answers (2)

user506069
user506069

Reputation: 781

If the main concern is typos, just define your names as constants (something like public static final String DATASOURCE1 = "datasource1";) and consistently use those in your qualifier annotations instead of string literals. No need to add new classes or interfaces.

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 692131

A more standard way of doing this and avoid typos in qualifier names is to avoid using qualifier names, and instead use Qualifier annotations:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface DB1 {
}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface DB2 {
} 

and then, when defining your beans:

@Configuration
@ComponentScan
public class AppConfig {
  @Bean
  @ConfigurationProperties(prefix = "db1.datasource")
  @DB1
  public DataSource dataSource1() {
    return DataSourceBuilder.create().build();
  }

  @Bean
  @DB1
  public JdbcTemplate db1(@DB1 DataSource ds) {
    return new JdbcTemplate(ds);
  }

  @Bean
  @ConfigurationProperties(prefix = "db2.datasource")
  @DB2
  public DataSource dataSource2() {
    return DataSourceBuilder.create().build();
  }

  @Bean
  @DB2
  public JdbcTemplate db1(@DB2 DataSource ds) {
    return new JdbcTemplate(ds);
  }
}

And finally when injecting your beans:

public class MyRepository(@DB1 JdbcTemplate db1, @DB2 JdbcTemplate db2) {
    ...
}

Upvotes: 2

Related Questions