Spring boot @Qualifier doesn't work with datasources

13,034

Solution 1

Declare one of your DataSource as @Primary.

Also you have 2 beans of same type - LocalContainerEntityManagerFactoryBean, declare one of them @Primary as well, as follows:

@Configuration
public static class PersistenceConfiguration {

        @Bean
        @Primary
        public DataSource ds1() {
            return new EmbeddedDatabaseBuilder().build();
        }

        @Bean
        public DataSource ds2() {
            return new EmbeddedDatabaseBuilder().build();
        }

        @Bean
        @Primary
        @Autowired
        public LocalContainerEntityManagerFactoryBean emfb(@Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
            return emfb.dataSource(ds)
                    .packages(DemoApplication.class)
                    .persistenceUnit("ds1")
                    .build();
        }

        @Bean
        @Autowired
        public LocalContainerEntityManagerFactoryBean emfb2(@Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
            return emfb.dataSource(ds)
                    .packages(DemoApplication.class)
                    .persistenceUnit("ds2")
                    .build();
        }
 }

Solution 2

The error is indicating that at some point in the application, a bean is being injected by the type DataSource and not being qualified by name at that point.

It does not matter that you have added @Qualifier in one location. The injection is failing in some other location that has not been qualified. It's not your fault though because that location is in Spring Boot's DataSourceAutoConfiguration which you should be able to see in your stack trace, below the piece that you have posted.

I would recommend excluding DataSourceAutoConfiguration i.e. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class). Otherwise, this configuration is only being applied to the bean you have made @Primary. Unless you know exactly what that is, it is likely to result in subtle and unexpected differences in behaviour between your DataSources.

Share:
13,034

Related videos on Youtube

Tuomas Toivonen
Author by

Tuomas Toivonen

Updated on September 15, 2022

Comments

  • Tuomas Toivonen
    Tuomas Toivonen about 1 year

    I'm building JPA configuration with multiple persistence units using different in-memory datasources, but the configuration fails resolving the qualified datasource for entity manager factory bean with the following error:

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Parameter 0 of method emfb in datasources.Application$PersistenceConfiguration required a single bean, but 2 were found:
            - ds1: defined by method 'ds1' in class path resource [datasources/Application$PersistenceConfiguration.class]
            - ds2: defined by method 'ds2' in class path resource [datasources/Application$PersistenceConfiguration.class]
    
    
    Action:
    
    Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
    

    Here is the sample application

    package datasources;
    
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    import javax.persistence.PersistenceContextType;
    import javax.sql.DataSource;
    import javax.ws.rs.ApplicationPath;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import org.apache.log4j.Logger;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
    import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    import org.springframework.stereotype.Component;
    
    @Configuration
    @EnableAutoConfiguration(exclude = {
    //      HibernateJpaAutoConfiguration.class,
    //      DataSourceAutoConfiguration.class
            JtaAutoConfiguration.class
    })
    @ComponentScan
    public class Application {
    
        public static void main(String[] args) {
    
            new SpringApplicationBuilder(Application.class)
                .build()
                .run(args);
        }
    
        @Component
        @Path("/ds")
        public static class DsApi {
    
            private final static Logger logger = Logger.getLogger(DsApi.class);
    
            @Autowired(required = false)
            @Qualifier("ds1")
            private DataSource ds;
    
            @GET
            public String ds() {
                logger.info("ds");
                return ds.toString();
            }
        }
    
        @Component
        @Path("/em")
        public static class EmApi {
    
            private final static Logger logger = Logger.getLogger(EmApi.class);
    
            @PersistenceContext(unitName = "ds2", type = PersistenceContextType.TRANSACTION)
            private EntityManager em;
    
            @GET
            public String em() {
                logger.info("em");
                return em.toString();
            }
        }
    
        @Configuration
        @ApplicationPath("/jersey")
        public static class JerseyConfig extends ResourceConfig {
            public JerseyConfig() {
                register(DsApi.class);
                register(EmApi.class);
            }
        }
    
        @Configuration
        public static class PersistenceConfiguration {
    
            @Bean
            @Qualifier("ds1")
            public DataSource ds1() {
                return new EmbeddedDatabaseBuilder().build();
            }
    
            @Bean
            @Qualifier("ds2")
            public DataSource ds2() {
                return new EmbeddedDatabaseBuilder().build();
            }
    
            @Bean
            @Primary
            @Autowired
            public LocalContainerEntityManagerFactoryBean emfb(@Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
                return emfb.dataSource(ds)
                        .packages(Application.class)
                        .persistenceUnit("ds1")
                        .build();
            }
    
            @Bean
            @Autowired
            public LocalContainerEntityManagerFactoryBean emfb2(@Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
                return emfb.dataSource(ds)
                        .packages(Application.class)
                        .persistenceUnit("ds2")
                        .build();
            }
        }
    }
    
  • Tuomas Toivonen
    Tuomas Toivonen almost 7 years
    This will inject ds1 to emfb2, which needs ds2
  • Arpit Aggarwal
    Arpit Aggarwal almost 7 years
    It should not as we are specifying the @Qualifier("ds2") while creating emfb2
  • Tuomas Toivonen
    Tuomas Toivonen almost 7 years
    Indeed, you are right! But why Qualifier requires Primary to work? Also, why the same wiring worked fine without Primary, if those ds methods returned some other type instead (And of course emfb methods refactored to consume that type)?
  • Arpit Aggarwal
    Arpit Aggarwal almost 7 years
  • granadaCoder
    granadaCoder about 4 years
    Could you please add the import statement(s)?