Spring boot @Qualifier 不适用于数据源

Posted

技术标签:

【中文标题】Spring boot @Qualifier 不适用于数据源【英文标题】:Spring boot @Qualifier doesn't work with datasources 【发布时间】:2017-05-15 20:43:55 【问题描述】:

我正在使用不同的内存中数据源构建具有多个持久性单元的JPA配置,但配置无法解析合格的数据源 实体管理器工厂 bean 出现以下错误:

***************************
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

这里是示例应用程序

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();
        
    

【问题讨论】:

也许它不能在静态类中工作? 不可能,因为我试图用任意类型替换那些 DataSource bean,并且依赖关系得到了正确解决。还尝试以相同的错误返回手动实例化的 DataSource (No builder)。 【参考方案1】:

将您的DataSource 之一声明为@Primary

另外你有 2 个相同类型的 bean - LocalContainerEntityManagerFactoryBean,同时声明其中一个 @Primary,如下:

@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();
        
 

【讨论】:

这会将ds1注入到需要ds2的emfb2中 不应该因为我们在创建emfb2时指定@Qualifier("ds2") 确实,你是对的!但是为什么 Qualifier 需要 Primary 才能工作?另外,如果那些 ds 方法返回了其他类型(当然 emfb 方法被重构以使用该类型),为什么没有 Primary 的相同接线工作正常? 希望有帮助 - docs.spring.io/spring-framework/docs/current/javadoc-api/org/… 能否添加进口声明?【参考方案2】:

错误表明在应用程序中的某个时间点,一个 bean 被DataSource 类型注入,而 在那个点没有被名称限定。

在一个位置添加@Qualifier 并不重要。注入在某些未合格的 other 位置失败。但这不是您的错,因为该位置位于 Spring Boot 的 DataSourceAutoConfiguration 中,您应该能够在堆栈跟踪中看到,位于您发布的部分下方。

我建议排除DataSourceAutoConfiguration,即@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)。否则,此配置仅应用于您创建的 bean @Primary。除非您确切知道那是什么,否则您的DataSources 之间的行为可能会出现微妙和意想不到的差异。

【讨论】:

【参考方案3】:

尝试在静态类之外声明数据源 bean。即直接在Application.java中

【讨论】:

以上是关于Spring boot @Qualifier 不适用于数据源的主要内容,如果未能解决你的问题,请参考以下文章

Dagger2 Qualifier 不适用于 Kotlin?

定制的 ObjectMapper 不适用于 spring boot hatoas

Lombok 不适用于 spring-boot-maven

Spring-Boot 不适用于 Flyway

跨域不适用于 Spring Boot

JSF 注释不适用于 Spring-boot