将 spring-boot-starter-web 添加到依赖项会破坏多个数据源

Adding spring-boot-starter-web to dependencies breaks multiple datasources

我有一个包含 3 个不同数据源的项目。如果项目是 运行 来自 spring-boot:运行 且仅具有这些依赖项,则它工作正常:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.6.RELEASE</version>
</parent>
<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>RELEASE</version>
    </dependency>

    <!--<dependency>-->
        <!--<groupId>org.springframework.boot</groupId>-->
        <!--<artifactId>spring-boot-starter-web</artifactId>-->
    <!--</dependency>-->

这是一个数据源,它们几乎都是一样的,只是更改了 bean 名称和数据库信息

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "emfIntranet", transactionManagerRef = "tmIntranet", basePackages = {"com.vnt.intranet.repositories"})
@ConfigurationProperties(prefix = "databases.sistemas")
public class IntranetPersistence {

    private String address;
    private String schema;
    private String username;
    private String password;
    private String eclipselinklog;
    private Boolean sqllog;

    @Primary
    @Bean(name = "dsIntranet")
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setUrl("jdbc:postgresql://" + address + "/" + schema);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setInitialSize(3);
        dataSource.setMaxIdle(10);
        dataSource.setMaxActive(10);
        return dataSource;
    }

    private EclipseLinkJpaVendorAdapter getEclipseLinkJpaVendorAdapter() {
        EclipseLinkJpaVendorAdapter vendorAdapter = new EclipseLinkJpaVendorAdapter();
        vendorAdapter.setDatabasePlatform("org.eclipse.persistence.platform.database.PostgreSQLPlatform");
        vendorAdapter.setShowSql(sqllog);
        return vendorAdapter;
    }

    @Primary
    @Bean(name = "emfIntranet")
    public EntityManagerFactory entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setJpaVendorAdapter(getEclipseLinkJpaVendorAdapter());
        factoryBean.setDataSource(dataSource());
        factoryBean.setPackagesToScan("com.vnt.intranet.entities");
        factoryBean.setPersistenceUnitName("intranet");

        Properties jpaProperties = new Properties();
        jpaProperties.put("eclipselink.weaving", "false");
        jpaProperties.put("eclipselink.logging.level", eclipselinklog); // SEVERE / FINEST

        factoryBean.setJpaProperties(jpaProperties);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Primary
    @Bean(name = "tmIntranet")
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory());
        return transactionManager;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSchema() {
        return schema;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEclipselinklog() {
        return eclipselinklog;
    }

    public void setEclipselinklog(String eclipselinklog) {
        this.eclipselinklog = eclipselinklog;
    }

    public Boolean getSqllog() {
        return sqllog;
    }

    public void setSqllog(Boolean sqllog) {
        this.sqllog = sqllog;
    }
}

我可以毫无问题地访问所有数据源...其中一个用@Primary 注释。

但是如果我取消注释 spring-boot-starter-web 依赖项它会破坏它并给我:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: more than one 'primary' bean found among candidates: [emfIntranet, entityManagerFactory, emfMkRadius, emfMkData]

我正在尝试将其转换为 Web 项目,但没有成功...

有什么想法吗?

编辑 为清楚起见添加其他 classes:

MkDataPersistence.class

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "emfMkData", transactionManagerRef = "tmMkData", basePackages = {"org.example.mkdata.repositories"})
@ConfigurationProperties(prefix = "databases.mkdata")
public class MkDataPersistence {

    private String address;
    private String schema;
    private String username;
    private String password;
    private String eclipselinklog;
    private Boolean sqllog;

    @Bean(name = "dsMkData")
    javax.sql.DataSource dataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl("jdbc:postgresql://" + address + "/" + schema);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setInitialSize(3);
        dataSource.setMaxIdle(10);
        dataSource.setMaxActive(10);
        return dataSource;
    }

    @Bean
    HibernateJpaVendorAdapter getHibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL9Dialect");
        vendorAdapter.setShowSql(sqllog);
        return vendorAdapter;
    }

    @Bean(name = "emfMkData")
    EntityManagerFactory entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
        factoryBean.setDataSource(dataSource());
        factoryBean.setPackagesToScan("org.example.mkdata.entities");
        factoryBean.setPersistenceUnitName("mkdata");

        Properties jpaProperties = new Properties();
        jpaProperties.put("eclipselink.weaving", "false");
        jpaProperties.put("eclipselink.logging.level", eclipselinklog); // SEVERE / FINEST

        factoryBean.setJpaProperties(jpaProperties);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Bean(name = "tmMkData")
    PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory());
        return transactionManager;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSchema() {
        return schema;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEclipselinklog() {
        return eclipselinklog;
    }

    public void setEclipselinklog(String eclipselinklog) {
        this.eclipselinklog = eclipselinklog;
    }

    public Boolean getSqllog() {
        return sqllog;
    }

    public void setSqllog(Boolean sqllog) {
        this.sqllog = sqllog;
    }
}

MkRadiusPersistence.class

@Configuration
@EnableTransactionManagement()
@EnableJpaRepositories(entityManagerFactoryRef = "emfMkRadius", transactionManagerRef = "tmMkRadius", basePackages = {"org.example.mkradius.repositories"})
@ConfigurationProperties(prefix = "databases.mkradius")
public class MkRadiusPersistence {

    private String address;
    private String schema;
    private String username;
    private String password;
    private String eclipselinklog;
    private Boolean sqllog;

    @Bean(name = "dsMkRadius")
    javax.sql.DataSource dataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUrl("jdbc:postgresql://" + address + "/" + schema);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setInitialSize(3);
        dataSource.setMaxIdle(10);
        dataSource.setMaxActive(10);
        return dataSource;
    }

    @Bean
    HibernateJpaVendorAdapter getHibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL9Dialect");
        vendorAdapter.setShowSql(sqllog);
        return vendorAdapter;
    }

    @Bean(name = "emfMkRadius")
    EntityManagerFactory entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
        factoryBean.setDataSource(dataSource());
        factoryBean.setPackagesToScan("org.example.mkradius.entities");
        factoryBean.setPersistenceUnitName("mkradius");

        Properties jpaProperties = new Properties();
        jpaProperties.put("eclipselink.weaving", "false");
        jpaProperties.put("eclipselink.logging.level", eclipselinklog); // SEVERE / FINEST

        factoryBean.setJpaProperties(jpaProperties);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Bean(name = "tmMkRadius")
    PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory());
        return transactionManager;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSchema() {
        return schema;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEclipselinklog() {
        return eclipselinklog;
    }

    public void setEclipselinklog(String eclipselinklog) {
        this.eclipselinklog = eclipselinklog;
    }

    public Boolean getSqllog() {
        return sqllog;
    }

    public void setSqllog(Boolean sqllog) {
        this.sqllog = sqllog;
    }
}

编辑 2

Application.class

@Configuration
@ComponentScan(basePackages = { "org.example.startup" })
@EnableAutoConfiguration
public class Application {
    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    @Autowired
    CableRouteRepository cableRouteRepository;

    @Autowired
    CityRepository cityRepository;

    @Autowired
    RadAcctRepository radAcctRepository;

    public static void main(String[] args) {

        ConfigurableApplicationContext context = new SpringApplicationBuilder()
                .showBanner(false)
                .sources(Application.class)
                .run(args);

        Application app = context.getBean(Application.class);

//        for (String bean: context.getBeanDefinitionNames()) {
//            logger.info(bean);
//        }

        app.start();
    }

    private void start() {
        logger.info("Application.start()");

        logger.info("{}", cableRouteRepository.findAll());
        logger.info("{}", cityRepository.findAll());
        logger.info("{}", radAcctRepository.findTest());
    }


}

这是启动器 class...我打印了每个存储库作为测试(这里的每个存储库都在不同的数据源上)...只要我没有 spring-starter-web 在 class 路径上。

编辑 3

Github 回购 https://github.com/mtrojahn/test-multiple-databases

我希望我做对了......我从来没有真正与 Github 一起工作过:)

编辑 4

Github 使用失败代码正确更新。

提醒一下,如果对下面的依赖项进行了注释,则代码有效:

<dependency>
    <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

并且如果上面的依赖项未被注释但下面的代码在 IntranetPersistence.class 中被更改为:

@Primary
@Bean(name = "emfIntranet")

@Primary
@Bean(name = "entityManagerFactory")

它覆盖默认 bean 并开始失败:

Caused by: java.lang.IllegalArgumentException: Not an managed type: class org.example.intranet.entities.CableRoute

您受到 Spring Boot 1.2 的 JPA 自动配置行为的影响。如果存在用户定义的 LocalContainerEntityManagerFactoryBean,它只会关闭自己的 entityManagerFactory bean 的创建。您正在使用 LocalContainerEntityManagerFactoryBean 但自己调用 afterPropertiesSetgetObject 而不是让容器为您这样做。这使得上下文将有多个 @Primary EntityManagerFactory bean。这是 improved in Spring Boot 1.3 以便用户声明的 EntityManagerFactory bean 也将关闭自动配置。

这在尝试创建 openEntityManagerInViewInterceptor 时会导致问题,因为它需要一个 EntityManagerFactory 并且上下文无法知道它应该选择两个 @Primary bean 中的哪一个。

有几种方法可以继续。您可以更新配置以定义类型为 LocalContainerEntityManagerFactoryBeans 而不是 EntityManagerFactory 的 bean。另一种方法是通过将以下内容添加到您的 application.yml:

来禁用拦截器的创建
spring:
  jpa:
    open_in_view: false