Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot @Qualifier doesn't work with datasources

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();
        }
    }
}
like image 939
Tuomas Toivonen Avatar asked Jan 01 '17 11:01

Tuomas Toivonen


2 Answers

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.

like image 65
Rupert Madden-Abbott Avatar answered Nov 18 '22 09:11

Rupert Madden-Abbott


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();
        }
 }
like image 3
Arpit Aggarwal Avatar answered Nov 18 '22 09:11

Arpit Aggarwal