0

I'm trying to set up a spring boot project with two datasources. First datasource would be a H2 Database and second a MapRepository. Both repositories would share the same entity.

I could manage to setup a project with two H2 databases, but when I try to setup a MapRepository instead of the second H2 datasource I get the following error:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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.

I already tried to add the ComponentScan and add a repository annotation to the MapRepository, but couldn't figure out why no bean was created for it. Both repositories are in seperate packages which are set as basePackages for the EnableMapRepositories/EnableJpaRepositories annotation. For the SQLRepository I created a configuration class with the driver properties etc. I am not sure if something like this would also be needed for the MapRepositories and couldn't find helpful documentation about it.

I am not really experienced with Spring Boot therefore the first question would be if its possible to have a setup like this? And if yes how am I supposed to configure it?

Application start:

@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 {

}

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 in which I tried to access both repositories:

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

BasicEntity:

    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;
        
    }
        

SQLConfiguration:

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 {

}

application.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
Ina93
  • 11
  • 1
  • Hello, and welcome to StackOverflow! Take a look on this one: https://www.baeldung.com/spring-data-jpa-multiple-databases - it's well explained and provides a nice and clean way to achieve what you are searching for. Please consider that it's mostly an https://xyproblem.info/ and maybe you want two services instead. – Clijsters Jan 12 '21 at 10:47
  • Hello Clijsters, thanks for the quick reply. I already checked this tutorial but it doesn't contain anything about the MapRepositories. What would for example the jdbcUrl look like for a MapRepository? I would assume that it's not necessary to give an url, driver etc at all... – Ina93 Jan 12 '21 at 12:43
  • The tutorial mainly describes how to add two _datasources_, two repositories aren't any special at all. Just create two Repository beans. Where exactly is your problem? – Clijsters Jan 12 '21 at 16:09
  • Is it intentional that your Repository Bean is annotated with `@NoRepositoryBean`? – Clijsters Jan 12 '21 at 16:10
  • Yes, as written in my question I was able to add two normal repositories. But I can't figure out how to add a MapRepository. What would the url and driver be for a MapRepository if I can add it the same way as a JPARepository? Also the properties for the annotation of a MapRepository are different. Couldn't find anywhere a Configuration File for a MapRepository. :) – Ina93 Jan 14 '21 at 12:17
  • A Repository and a Datasource is not the same. Some Repositories don't need Datasources.What's special on your MapRepository? IS your source code publicly available? – Clijsters Jan 15 '21 at 10:34

1 Answers1

2

As written in my comment, it's quite unusual to use two different datasources with one spring boot service. But here are cases where this might be necessary, and here is how to achieve it in a clean way:

Keep in mind, my answer is mostly taken from that Baeldung tutorial

Consider having a datasource like this:

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

Now, we want to add a second one, preferably with the same syntax:

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

To use both configurations simoultanously, we just create two configuration classes with a Datasource Bean - pay attention to the prefix annotation:

@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
}

You could imo just create one configuration class and provide both beans in that one.

For more information see Spring JPA – Multiple Databases

Clijsters
  • 4,031
  • 1
  • 27
  • 37