Как настроить Spring Boot с двумя источниками данных (MapRepository и H2 JPARepository)?

Я пытаюсь настроить весенний загрузочный проект с двумя источниками данных. Первым источником данных будет база данных H2, а вторым — MapRepository. Оба репозитория будут использовать одну и ту же сущность.

Мне удалось настроить проект с двумя базами данных H2, но когда я пытаюсь настроить MapRepository вместо второго источника данных H2, я получаю следующую ошибку:

        .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.0)

2021-01-12 10:57:16.610  INFO 26672 --- [           main] ch.getonline.springtestapp.App           : Starting App using Java 15.0.1 on nbbetina1 with PID 26672 (C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes started by BetinaHiestand in C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app)
2021-01-12 10:57:16.612  INFO 26672 --- [           main] ch.getonline.springtestapp.App           : The following profiles are active: dev
2021-01-12 10:57:17.070  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-01-12 10:57:17.070  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Map repositories in DEFAULT mode.
2021-01-12 10:57:17.092  INFO 26672 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data Map - Could not safely identify store assignment for repository candidate interface ch.getonline.springtestapp.storage.repositories.map.MapRepository. If you want this repository to be a Map repository, consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.KeyValueRepository.
2021-01-12 10:57:17.092  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Map repository interfaces.
2021-01-12 10:57:17.094  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2021-01-12 10:57:17.094  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-01-12 10:57:17.111  INFO 26672 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 14 ms. Found 1 JPA repository interfaces.
2021-01-12 10:57:17.654  INFO 26672 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-01-12 10:57:17.661  INFO 26672 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-01-12 10:57:17.661  INFO 26672 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.39]
2021-01-12 10:57:17.758  INFO 26672 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-01-12 10:57:17.758  INFO 26672 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1105 ms
2021-01-12 10:57:17.976  INFO 26672 --- [           main] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/dbadmin'. Database available at 'jdbc:h2:mem:db1dev'
2021-01-12 10:57:18.058  INFO 26672 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-01-12 10:57:18.099  INFO 26672 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.23.Final
2021-01-12 10:57:18.198  INFO 26672 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-01-12 10:57:18.324  INFO 26672 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate: 
    
    drop table if exists "BasicEntity" CASCADE 
Hibernate: 
    
    create table "BasicEntity" (
       "DNA" binary not null,
        "id" varchar(255),
        "type" varchar(255),
        primary key ("DNA")
    )
2021-01-12 10:57:18.759  INFO 26672 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-01-12 10:57:18.765  INFO 26672 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.787  INFO 26672 --- [           main] ch.getonline.springtestapp.App           : SpringTestApplication is starting...
2021-01-12 10:57:18.931  WARN 26672 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appContext': Unsatisfied dependency expressed through field 'entityStorage'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'entityStorageHandler' defined in file [C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes\ch\getonline\springtestapp\storage\handlers\EntityStorageHandler.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2021-01-12 10:57:18.931  INFO 26672 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.933  INFO 26672 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2021-01-12 10:57:18.944  INFO 26672 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-01-12 10:57:18.956 ERROR 26672 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 2 of constructor in ch.getonline.springtestapp.storage.handlers.EntityStorageHandler required a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' in your configuration.

Я уже пытался добавить ComponentScan и добавить аннотацию репозитория в MapRepository, но не мог понять, почему для него не был создан bean-компонент. Оба репозитория находятся в отдельных пакетах, которые устанавливаются как basePackages для аннотации EnableMapRepositories/EnableJpaRepositories. Для SQLRepository я создал класс конфигурации со свойствами драйвера и т. д. Я не уверен, что что-то подобное также потребуется для MapRepositories, и не смог найти полезную документацию по этому поводу.

Я не очень разбираюсь в Spring Boot, поэтому первый вопрос: возможно ли иметь такую ​​​​настройку? И если да, то как мне его настроить?

Запуск приложения:

      @SpringBootApplication
@ComponentScan (basePackages = {"ch.getonline.springtestapp"})
@EntityScan("ch.getonline.springtestapp.entity.types")
@EnableMapRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.map")
@EnableJpaRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.sql", entityManagerFactoryRef = "sqlDatabaseEntityManager", transactionManagerRef = "sqlDatabaseTransactionManager")
public class App  {
    
    // Logger setup (Per class) 
    private static final Logger log = LoggerFactory.getLogger(App.class);
            
    /*
     *  Application start
     */
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(App.class, args);
        System.out.println("App context in main: " + ctx.getDisplayName());
    }
    

MapRepository:

      package ch.getonline.springtestapp.storage.repositories.map;



import org.springframework.stereotype.Repository;

import ch.getonline.springtestapp.storage.repositories.EntityRepository;

@Repository("mapRepository")
public interface MapRepository extends EntityRepository {

}

СущностьРепозиторий:

      package ch.getonline.springtestapp.storage.repositories;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;

import ch.getonline.springtestapp.entity.types.BasicEntity;

@NoRepositoryBean
public interface EntityRepository extends CrudRepository<BasicEntity, Long>{
    //Entity findByUuid(UUID id);
}

StorageHandler, в котором я пытался получить доступ к обоим репозиториям:

          package ch.getonline.springtestapp.storage.handlers;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.UUID;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    
    import ch.getonline.springtestapp.AppContext;
    import ch.getonline.springtestapp.entity.attribute.Attribute;
    import ch.getonline.springtestapp.entity.types.BasicEntity;
    import ch.getonline.springtestapp.storage.StorageHandler;
    import ch.getonline.springtestapp.storage.repositories.EntityRepository;
    import ch.getonline.springtestapp.storage.repositories.map.MapRepository;
    import ch.getonline.springtestapp.storage.repositories.sql.SQLRepository;
    
    
    
    
    
    /** Entity Storage
     * <br>
     * 
     * - Coordinates saving, loading, updating of Entities over different Repositories
     * 
     * 
     * @author sigi
     *
     */
    
    @Component
    public class EntityStorageHandler implements StorageHandler<BasicEntity, Long> {
        
        
        // Logger
        private static final Logger log = LoggerFactory.getLogger(EntityStorageHandler.class);
    
        private final AppContext app;
        private final Map<String, EntityRepository> repos;
        EntityStorageHandler(AppContext app,  SQLRepository sqlRepo, MapRepository mapRepo) {
            this.app = app;
            this.repos = new HashMap<String, EntityRepository>();
            this.repos.put("sql", sqlRepo);
            this.repos.put("map", mapRepo);
        }
        
        
        
        //StorageHandler start hook
        public void run(String... args) throws Exception {
            
            //Print all configs for the key app in the config
            StringBuilder appConfig = new StringBuilder();
            for(Entry<String, Object> entry : this.app.getConfig().entrySet()) {
                appConfig.append("\nkey: " + entry.getKey() + " value: " + entry.getValue());
            }
            log.info(appConfig.toString());
            
            
            //Write demo Entity into db 
            BasicEntity e1 = new BasicEntity();
            e1.setId("1");
            e1.setType("Type1");
            this.repos.get("sql").save(e1);
            
            BasicEntity e2 = new BasicEntity();
            e2.setId("2");
            e2.setType("Type2");
            this.repos.get("sql").save(e2);
            
            BasicEntity e3 = new BasicEntity();
            e3.setId("3");
            e3.setType("Type3");
            this.repos.get("map").save(e2);
        }
    }
        

Базовая сущность:

          package ch.getonline.springtestapp.entity.types;
    
    import java.util.UUID;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    
    import org.springframework.data.keyvalue.annotation.KeySpace;
    
    import ch.getonline.springtestapp.entity.GenericEntity;
    
    
    
    /**
     * Basic Entity Implementation
     * 
     * @author sigi
     *
     */
    
    @Entity
    @KeySpace("basicEntities")
    public class BasicEntity extends GenericEntity {
    
        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        UUID DNA;
        
    }
        

SQLКонфигурация:

      package ch.getonline.springtestapp.configuration;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;


@Configuration
public class SQLConfiguration {
    @Autowired
    private Environment env;

    public SQLConfiguration() {
        super();
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean sqlDatabaseEntityManager() {
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(sqlDataSource());
        em.setPackagesToScan("ch.getonline.springtestapp.entity.types");

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        final HashMap<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
        properties.put("hibernate.dialect", env.getProperty("spring.jpa.database-platform"));
        properties.put("hibernate.jdbc.batch_size", env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size"));
        properties.put("hibernate.order_inserts", env.getProperty("spring.jpa.properties.hibernate.order_inserts"));
        properties.put("hibernate.order_updates", env.getProperty("spring.jpa.properties.hibernate.order_updates"));
        properties.put("hibernate.jdbc.batch_versioned_data", env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_versioned_data"));
        properties.put("hibernate.generate_statistics", env.getProperty("spring.jpa.properties.hibernate.generate_statistics"));
        properties.put("hibernate.id.new_generator_mappings", env.getProperty("spring.jpa.properties.hibernate.id.new_generator_mappings"));
        properties.put("hhibernate.cache.use_second_level_cache", env.getProperty("spring.jpa.properties.hibernate.cache.use_second_level_cache"));
        properties.put("hibernate.globally_quoted_identifiers", env.getProperty("spring.jpa.properties.hibernate.globally_quoted_identifiers"));
        properties.put("hibernate.format_sql", env.getProperty("spring.jpa.properties.hibernate.format_sql"));
        properties.put("hibernate.show_sql", env.getProperty("spring.jpa.properties.hibernate.show_sql"));
        properties.put("hibernate.use_sql_comments", env.getProperty("spring.jpa.properties.hibernate.use_sql_comments"));
        properties.put("hibernate.type", env.getProperty("spring.jpa.properties.hibernate.type"));
        properties.put("hibernate.naming.physical-strategy", env.getProperty("spring.jpa.hibernate.naming.physical-strategy"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Primary
    @Bean
    public DataSource sqlDataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.password"));

        return dataSource;
    }

    @Primary
    @Bean
    public PlatformTransactionManager sqlDatabaseTransactionManager() {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(sqlDatabaseEntityManager().getObject());
        return transactionManager;
    }

}

SQLRepository

      package ch.getonline.springtestapp.storage.repositories.sql;

import ch.getonline.springtestapp.storage.repositories.EntityRepository;


public interface SQLRepository extends EntityRepository {

}

приложение.yml

      #Debug mode
debug: false



#External config
spring:
  #Basic setup
  profiles.active: dev
  config:
    import: optional:classpath:config/app.properties, optional:classpath:config/config.yml
  
  #Localization
  messages:
    basename: config.i18n.messages
  
  #db
  h2: 
    console:
      path: /dbadmin
      enabled: true
      settings:
        web-allow-others: true

  datasource:
      username: inmemory
      password: inmemory
      driverClassName: org.h2.Driver
      port: 8080



  #jpa
  jpa:
    hibernate:
      ddl-auto: create
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        id:
          new_generator_mappings: true
        cache: 
          use_second_level_cache: true
        order_inserts: true
        order_updates: true
        globally_quoted_identifiers: true
        generate_statistics: false
        show_sql: true
        format_sql: true
        use_sql_comments: true
        type: trace
        jdbc:
          batch_size: 500
          batch_versioned_data: false
        tmp:
          use_jdbc_metadata_defaults: false
    database-platform: org.hibernate.dialect.H2Dialect

1 ответ

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

Имейте в виду, что мой ответ в основном взят из этого

Рассмотрим такой источник данных:

      spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]

Теперь мы хотим добавить второй, желательно с таким же синтаксисом:

      spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]

Чтобы использовать обе конфигурации одновременно, мы просто создаем два класса конфигурации с DatasourceBean — обратите внимание на префиксную аннотацию:

      @Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.user",
  entityManagerFactoryRef = "userEntityManager",
  transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
    
    @Primary
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }
    // userEntityManager bean 

    // userTransactionManager bean
}


@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.product", 
  entityManagerFactoryRef = "productEntityManager", 
  transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
   
    @Bean
    @ConfigurationProperties(prefix="spring.second-datasource")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }
   
    // productEntityManager bean 

    // productTransactionManager bean
}

Вы можете просто создать один класс конфигурации и предоставить в нем оба компонента.

Для получения дополнительной информации см. учебника Baeldung.Spring JPA — несколько баз данных .

Другие вопросы по тегам