【问题标题】:Adding spring-boot-starter-web to dependencies breaks multiple datasources将 spring-boot-starter-web 添加到依赖项会破坏多个数据源
【发布时间】:2016-01-15 16:53:48
【问题描述】:

我有一个包含 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]

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

有什么想法吗?

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

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

应用程序类

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


}

这是入门类...​​我打印了每个存储库作为测试(这里的每个存储库都在不同的数据源上)...只要我在类路径上没有 spring-starter-web,它们就可以正常工作.

编辑 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

【问题讨论】:

  • emfMkRadiusemfMkData 声明在哪里?我可以看到emfIntranet@Primary,也是另外两个之一吗?
  • 我会和他们一起编辑,但他们几乎是一样的,所以我没有把他们都发出去。
  • Spring Boot 的 entityManagerFactory bean 是 @Primary 但它应该被你的任何一个 EntityManagerFactory bean 关闭,因为它的创建取决于它们在上下文中不是 EntityManagerFactory bean .你的主要课程是什么样的?您是否直接引用任何 Boot 的自动配置类?
  • 主课对我来说看起来不错。你能在某个地方分享一个 repo,其中包含重现问题所需的所有代码吗?
  • 感谢您的关注! :) 我在测试用例中添加了一个 repo。

标签: spring-mvc spring-boot spring-data-jpa spring-data-rest


【解决方案1】:

您受到 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

【讨论】:

  • 是的,这似乎可以解决问题。感谢您的时间和兴趣:)
  • @Coderdood 我已经更新了我的答案,详细说明了正在发生的事情和原因。
  • 是的,我将 bean 更改为 LocalContainerEntityManagerFactoryBean 并且它成功了。现在甚至可以使用 spring.jpa.open_in_view = true 。谢谢!
猜你喜欢
  • 2017-03-19
  • 2015-12-28
  • 2017-06-17
  • 1970-01-01
  • 2016-04-09
  • 2018-01-10
  • 2021-04-29
  • 1970-01-01
  • 2018-03-10
相关资源
最近更新 更多