Зависимость Cycli c, если определено несколько источников данных - PullRequest
1 голос
/ 25 февраля 2020

Я хотел объявить два компонента DataSource и использовать один из них динамически, используя AbstractRoutingDataSource, который объявлен как компонент @Primary. Удивительно, но я не смог запустить свое приложение из-за циклической c зависимости:

org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
|  dataSource defined in <myclass>
↑     ↓
|  readOnlyDataSource defined in <myclass>
↑     ↓
|  org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘

Это связано с этой реализацией:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    @Primary
    DataSource dataSource(@Qualifier("firstDS") DataSource firstDS,
                          @Qualifier("secondDS") DataSource secondDS) {
        MyRoutingDataSource ds = new MyRoutingDataSource();
        ds.setCurrentDS(firstDS);
        return ds;
    }

    @Bean("firstDS")
    public DataSource firstDS(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    @Bean("secondDS")
    public DataSource secondDs(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    class MyRoutingDataSource extends AbstractRoutingDataSource {
        private DataSource currentDS;

        public void setCurrentDS(DataSource currentDS) {
            this.currentDS = currentDS;
        }

        @Override
        protected Object determineCurrentLookupKey() {
            return currentDS;
        }
    }
}

Обратите внимание, что я не Я не хочу исключать DataSourceAutoConfiguration - он предоставляет некоторые дополнительные функциональные возможности, которые я хочу использовать в своем проекте (например, DataSourceInitializer).

Не могли бы вы объяснить, почему это не работает? Я чувствую, что это сообщение об ошибке вводит в заблуждение. Между HibernateJpaConfiguration и DataSourceInitializerInvoker отсутствует зависимость cycli c. Они оба используют DataSource, основное определение которого я предоставляю.

Существует полный проект с этой проблемой: https://github.com/kozub/spring-dependency-management-bug

Ответы [ 2 ]

0 голосов
/ 28 апреля 2020

Здесь есть небольшая ошибка. Позвольте мне объяснить вам пример foo и bar DB вместе с git ссылкой на репо.

Вот как ваш application.properties выглядит как

# Oracle DB - "foo"
spring.datasource.url=jdbc:oracle:thin:@//db-server-foo:1521/FOO
spring.datasource.username=fooadmin
spring.datasource.password=foo123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# PostgreSQL DB - "bar"
bar.datasource.url=jdbc:postgresql://db-server-bar:5432/bar
bar.datasource.username=baradmin
bar.datasource.password=bar123
bar.datasource.driver-class-name=org.postgresql.Driver

Установите для SQL Dialect значение « default ”в вашем application.properties, чтобы Spring мог автоматически определять различные SQL диалекты каждого источника данных

spring.jpa.database=default

Пакет должен выглядеть примерно так:

src/main/java
- com.foobar
  - foo
    - domain
    - repo
  - bar
    - domain
    - repo

Вот основная часть. Классы конфигурации

Класс конфигурации Foo (Oracle)

package com.foobar;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  entityManagerFactoryRef = "entityManagerFactory",
  basePackages = { "com.foobar.foo.repo" }
)
public class FooDbConfig {

  @Primary
  @Bean(name = "dataSource")
  @ConfigurationProperties(prefix = "spring.datasource")
  public DataSource dataSource() {
    return DataSourceBuilder.create().build();
  }

  @Primary
  @Bean(name = "entityManagerFactory")
  public LocalContainerEntityManagerFactoryBean 
  entityManagerFactory(
    EntityManagerFactoryBuilder builder,
    @Qualifier("dataSource") DataSource dataSource
  ) {
    return builder
      .dataSource(dataSource)
      .packages("com.foobar.foo.domain")
      .persistenceUnit("foo")
      .build();
  }

  @Primary
  @Bean(name = "transactionManager")
  public PlatformTransactionManager transactionManager(
    @Qualifier("entityManagerFactory") EntityManagerFactory 
    entityManagerFactory
  ) {
    return new JpaTransactionManager(entityManagerFactory);
  }
}

Класс конфигурации Bar (postgres)

package com.foobar;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  entityManagerFactoryRef = "barEntityManagerFactory",
  transactionManagerRef = "barTransactionManager",
  basePackages = { "com.foobar.bar.repo" }
)
public class BarDbConfig {

  @Bean(name = "barDataSource")
  @ConfigurationProperties(prefix = "bar.datasource")
  public DataSource dataSource() {
    return DataSourceBuilder.create().build();
  }

  @Bean(name = "barEntityManagerFactory")
  public LocalContainerEntityManagerFactoryBean 
  barEntityManagerFactory(
    EntityManagerFactoryBuilder builder,
    @Qualifier("barDataSource") DataSource dataSource
  ) {
    return
      builder
        .dataSource(dataSource)
        .packages("com.foobar.bar.domain")
        .persistenceUnit("bar")
        .build();
  }
  @Bean(name = "barTransactionManager")
  public PlatformTransactionManager barTransactionManager(
    @Qualifier("barEntityManagerFactory") EntityManagerFactory
    barEntityManagerFactory
  ) {
    return new JpaTransactionManager(barEntityManagerFactory);
  }
}

Ваши репозитории будут выглядеть примерно так:

package com.foobar.bar.repo;

@Repository
public interface BarRepository extends JpaRepository<Bar, Long> {

  Bar findById(Long id); 

}


package com.foobar.foo.repo;

@Repository
public interface FooRepository extends JpaRepository<Foo, Long> {

  Foo findById(Long id); 

}

И все готово.

Вы можете ссылаться на код на github здесь

0 голосов
/ 27 апреля 2020

Я столкнулся с той же проблемой, что и у вас, с той разницей, что я не использую DataSourceAutoConfiguration.

Я не эксперт по Spring, поэтому не могу сообщить вам причину root. Но я смог заставить свой код работать, перейдя из чего-то вроде этого, которое похоже на то, что вы опубликовали:

@Bean
@Primary
DataSource dataSource(@Qualifier("firstDS") DataSource firstDS,
                      @Qualifier("secondDS") DataSource secondDS) {
    MyRoutingDataSource ds = new MyRoutingDataSource();
    ds.setFirstDS(firstDS);
    ds.setSecondDs(secondDS);
    return ds;
}

@Bean("firstDS")
    public DataSource firstDS() {
    return /*create first DS*/
}

@Bean("secondDS")
    public DataSource secondDs(DataSourceProperties properties) {
    return /*create second DS*/
}

На это:

@Bean
DataSource dataSource() {
    DataSource first = /*create first DS*/
    DataSource second = /*create second DS*/

    MyRoutingDataSource ds = new MyRoutingDataSource();
    ds.setFirstDS(first);
    ds.setSecondDs(second);
    return ds;
}

Как вы можете видеть Я смог решить эту проблему, имея только один компонент Spring типа DataSource. Я создал два «первых» и «вторых» источника данных внутри метода, который создает источник данных маршрутизации, чтобы они не были бинами Spring. Наличие только одного компонента типа DataSource избавило меня от ошибки циклической зависимости.

Это решило мою проблему, но вы также хотите использовать DataSourceAutoConfiguration.

Я думаю, что вы сможете достичь этого с помощью что-то вроде этого:

@Bean
DataSource dataSource(@Qualifier("firstDSproperties") DataSourceProperties firstDSprops,
                      @Qualifier("secondDSproperties") DataSourceProperties secondDSprops) {
    DataSource first = firstDSprops.initializeDataSourceBuilder().build();
    DataSource second = secondDSprops.initializeDataSourceBuilder().build();

    MyRoutingDataSource ds = new MyRoutingDataSource();
    ds.setCurrentDS(firstDS);
    return ds;
}

@Bean("firstDSproperties")
@ConfigurationProperties("datasource.first")
public DataSourceProperties firstDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean("secondDSproperties")
@ConfigurationProperties("datasource.second")
public DataSourceProperties secondDataSourceProperties() {
    return new DataSourceProperties();
}

Этот код делает два компонента типа DataSourceProperties вместо типа DataSource. С bean-компонентами DataSourceProperties вы все еще можете позволить Spring автоматически подключать конфигурацию, не вызывая (надеюсь) проблемы циклической зависимости, вызванной наличием нескольких bean-компонентов типа DataSource, зависящих друг от друга.

Я не тестировал это с DataSourceProperties я не использую DataSourceAutoConfiguration в моем коде. Но, основываясь на вашем коде, я думаю, что это может сработать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...