4

I have a question regarding Spring Boot and using a setup with multiple DataSources using JpaRepositories.

[4 Edits below]

The structure of the project looks like this: com/mycompany/schema/AbstractJpaConfig com/mycompany/schema/domain_A/AJpaConfiguration com/mycompany/schema/domain_A/entity/AEntity com/mycompany/schema/domain_A/repository/ARepository com/mycompany/schema/domain_B/BJpaConfiguration com/mycompany/schema/domain_B/entity/BEntity com/mycompany/schema/domain_B/repository/BRepository

So I have two domains (A and B), with the DataSource setup being handled separately.

The abstract JPA configuration class is used to reduce redundancy and uses a custom DataSourceManager which handles the DataSources:

public abstract class AbstractJpaConfiguration {

    private final DataSourceManager dataSources;

    public AbstractJpaConfiguration(DataSourceManager dataSources) {
        this.dataSources = dataSources;
    }

    protected abstract String persistenceUnitName();

    protected abstract Class<?> packageEntityClass();

    protected abstract DataSource useDataSource(DataSourceManager dataSources);

    public abstract LocalContainerEntityManagerFactoryBean entityManagerFactoryBean();

    public abstract PlatformTransactionManager transactionManagerBean();

    public abstract LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean();

    protected LocalContainerEntityManagerFactoryBean buildEntityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(useDataSource(dataSources));
        em.setPersistenceUnitName(persistenceUnitName() + "PU");
        em.setBeanName(persistenceUnitName() + "EntityManager");
        em.setPackagesToScan(packageEntityClass().getPackage().getName());
        em.setJpaPropertyMap(persistenceProperties());
        em.setJpaVendorAdapter(jpaVendorAdapter());
        return em;
    }

    protected Map<String, String> persistenceProperties() {
        Map<String, String> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "validate");
        return properties;
    }

    protected JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(false);
        adapter.setGenerateDdl(false);
        return adapter;
    }

    protected PlatformTransactionManager buildTransactionManager() {
        LocalContainerEntityManagerFactoryBean emfBean = getEntityManagerFactoryBean();
        EntityManagerFactory emf = emfBean.getObject();
        return new JpaTransactionManager(emf);
    }
}

An implementation of the configuration, located in the domain A package, looks like this:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.mycompany.schema.domain_A",
        entityManagerFactoryRef = "AEntityManagerFactory",
        transactionManagerRef = "ATransactionManager")
@EntityScan(basePackages = "com.mycompany.schema.domain_A")
@DependsOn("flywayMigrationInitializer")
public class AJpaConfiguration extends AbstractJpaConfiguration {

    @Autowired
    public AJpaConfiguration(DataSourceManager dataSources) {
        super(dataSources);
    }

    @Override
    protected Class<?> packageEntityClass() {
        return getClass(); // This class is located in the entity class package
    }

    @Override
    protected String persistenceUnitName() {
        return "a";
    }

    @Override
    protected DataSource useDataSource(DataSourceManager dataSources) {
        return dataSources.domainADataSource();
    }

    @Bean("aEntityManagerFactory")
    @Override
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
        return buildEntityManagerFactory();
    }

    @Bean("aTransactionManager")
    @Override
    public PlatformTransactionManager transactionManagerBean() {
        return buildTransactionManager();
    }

    @Override
    public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean() {
        return entityManagerFactoryBean();
    }
}

Then, the actual repository is defined as a JpaRepository:

@Repository
public interface ARepository extends JpaRepository<AEntity, Long> {
}

This seems to work, according to the application logs:

2018-12-14 09:45:02.997  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2018-12-14 09:45:02.997  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2018-12-14 09:45:03.012  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 9ms. Found 1 repository interface.
2018-12-14 09:45:03.029  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2018-12-14 09:45:03.029  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2018-12-14 09:45:03.085  INFO 13867 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 56ms. Found 1 repository interface.

And after that, and the successful Flyway migration, the Persistence Units are started:

2018-12-14 09:45:06.459  INFO 13867 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: aPU
    ...]
2018-12-14 09:45:06.544  INFO 13867 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate Core {5.3.7.Final}
2018-12-14 09:45:06.546  INFO 13867 --- [  restartedMain] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2018-12-14 09:45:06.746  INFO 13867 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
2018-12-14 09:45:06.922  INFO 13867 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
2018-12-14 09:45:07.976  INFO 13867 --- [  restartedMain] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
2018-12-14 09:45:08.066  INFO 13867 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'aPU'

But, when I try to autowire the repository into a service:

@Service
public class MyService {
    private final ARepository repository;

    @Autowired
    public MyService(ARepository repository) {
        this.repository = repository;
    }

    // ...
}

This is the error message I recieve in the logs:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'MyService' defined in URL [jar:file:...]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.mycompany.schema.domain_A.repository.ARepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:767) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:218) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1308) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1154) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:273) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1239) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:855) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:758) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    ... 106 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.mycompany.schema.domain_A.repository.ARepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1646) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1205) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:855) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:758) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    ... 120 common frames omitted

I hope the information provided can help solve this problem.
If not, I'll be happy to provide additional information.

Thank you for your time, and have a nice day
- Alexander

--- Edit ---

I put two breakpoints into the configuration classes, and got some new information:

The method for creating the LocalContainerEntityManagerFactoryBean is called, but the application stops due to the missing bean before the PlatformTransactionManager bean method is called.

What I overlooked was that the Service in question, which requires the JpaRepository subtype, is implementing the Spring Security UserDetailsManager interface. It seems like the Spring Security system is trying to instantiate the UserDetailsManager Service before the JpaRepository Beans picked up by @EnableJpaRepositories can be created.

Is there any solution for that?

--- Edit 2 ---

I tried to @Import the AJpaConfiguration class, which didn't change anything. However, looking closer at the bean instantiation logs I found the following messages:

2018-12-14 11:52:08.395 DEBUG 22417 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'aEntityManagerFactory'
2018-12-14 11:52:08.395 DEBUG 22417 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'aJpaConfig'

So Spring Boot is picking up the @Bean annotation for the LocalContainerEntityManagerFactoryBean, but neither creating EntityManagerFactory instances nor picking up the @Bean annotation for the PlatformTransactionManager.

--- Edit 3 ---

I set the @Autowired option required = false, and now it's picking up the beans and instantiating the repositories - but only after instantiating the services that require them, and thus not injecting them.

2018-12-14 13:25:30.484 DEBUG 31932 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'aEntityManager'
2018-12-14 13:25:33.035 DEBUG 31932 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'aTransactionManager'
2018-12-14 13:25:34.852 DEBUG 31932 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'aRepository'

How can I create the beans before everything else? That would seem to solve the problem.

--- Edit 4 ---

Thanks to the tip by @RobertNiestroj, I added @Lazy to the @Autowired annotations, which causes them to be initialized after then repositories.

But now, a different error appears:
java.lang.IllegalArgumentException: interface com.mycompany.schema.domain_A.repository.ARepository is not visible from class loader

I should also mention this Application is built out of Maven Modules: - Schema module (JPA configuration, repositories, and entity classes) - Core module (Application configuration and property classes) - [Other modules using Core and Schema] - Main module (Spring-Boot Application class)

8
  • 1
    You might want to post a Minimal, Complete, and Verifiable Examples. Please try your best to avoid posting too much code, it makes the question harder to read. Thanks Commented Dec 14, 2018 at 9:29
  • Hi Alexander, do you see the ARepository being created in the logs? Set your logs to logging.level.org.springframework.beans.factory=DEBUG in your application.properties and look for the line which says "Creating shared instance of singleton bean 'ARepository'". If you do not see the line it means that there is something wrong in creating your bean. i.e. component scan didn't pick it up. Commented Dec 14, 2018 at 9:36
  • Thanks @Rentius2407, I checked that and the repository is indeed not instantiated. I will check why that is the case, and how to get it to be created. Commented Dec 14, 2018 at 10:05
  • Edit after debugging with @Rentius2407 hint Commented Dec 14, 2018 at 10:35
  • Try to add @DependsOn("aRepository") to the service bean Commented Dec 14, 2018 at 12:55

4 Answers 4

0

Have you tried @Repository(name = "repository")? IIRC, by not providing a name, the component gets created with a name matching the class, in this case "aRepository". And thus when autowiring the repository variable, there's no component named "repository", resulting in the no qualifying bean exception.

Sign up to request clarification or add additional context in comments.

Comments

0

Setting up multiple data sources in a Spring Boot application using JPA (Java Persistence API) and JPARepository requires defining multiple DataSource beans, EntityManagerFactory, and TransactionManager beans.

Step 1: Define Database Properties

spring:
datasource:
primary:
  url: jdbc:mysql://localhost:3306/primary_db
  username: root
  password: password
  driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
  url: jdbc:mysql://localhost:3306/secondary_db
  username: root
  password: password
  driver-class-name: com.mysql.cj.jdbc.Driver

jpa: hibernate: ddl-auto: update show-sql: true`

Step 2: Define Entity Classes Each database will have its own set of entities. a) User b) Order

Step 3: Configure DataSources

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

private final JpaProperties jpaProperties;

@Autowired
public DataSourceConfig(JpaProperties jpaProperties) {
    this.jpaProperties = jpaProperties;
}

public DataSource createDataSource(String prefix) {
    return DataSourceBuilder.create()
            .url(jpaProperties.getProperties().get(prefix + ".url"))
            .username(jpaProperties.getProperties().get(prefix + ".username"))
            .password(jpaProperties.getProperties().get(prefix + ".password"))
            .driverClassName(jpaProperties.getProperties().get(prefix + ".driver-class-name"))
            .build();
}

public LocalContainerEntityManagerFactoryBean createEntityManagerFactory(
        EntityManagerFactoryBuilder builder, DataSource dataSource, String packageToScan, String unitName) {
    return builder
            .dataSource(dataSource)
            .packages(packageToScan)
            .persistenceUnit(unitName)
            .build();
}

public PlatformTransactionManager createTransactionManager(EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
}

}

a) Primary datasorce:

@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)

public class PrimaryDataSourceConfig {

private final DataSourceConfig dataSourceConfig;

@Autowired
public PrimaryDataSourceConfig(DataSourceConfig dataSourceConfig) {
    this.dataSourceConfig = dataSourceConfig;
}

@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource dataSource() {
    return dataSourceConfig.createDataSource("spring.datasource.primary");
}

@Primary
@Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier("primaryDataSource") DataSource dataSource) {
    return dataSourceConfig.createEntityManagerFactory(builder, 
dataSource, "com.example.entity.primary", "primary");
}

@Primary
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager transactionManager(
        @Qualifier("primaryEntityManagerFactory") EntityManagerFactory 
entityManagerFactory) {
    return 
dataSourceConfig.createTransactionManager(entityManagerFactory);
}
}

b) secondary datasource:

@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repository.secondary",
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDataSourceConfig {

private final DataSourceConfig dataSourceConfig;

@Autowired
public SecondaryDataSourceConfig(DataSourceConfig dataSourceConfig) {
    this.dataSourceConfig = dataSourceConfig;
}

@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource dataSource() {
    return 
dataSourceConfig.createDataSource("spring.datasource.secondary");
}

@Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier("secondaryDataSource") DataSource dataSource) {
    return dataSourceConfig.createEntityManagerFactory(builder, 
dataSource, "com.example.entity.secondary", "secondary");
}

@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager transactionManager(
        @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory 
entityManagerFactory) {
    return 
dataSourceConfig.createTransactionManager(entityManagerFactory);
}
}   

step-4 Define JPA Repositories

a) primary repo:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

}

b) secondary repo:

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

}

This should work as you intended to work. Happy coding.

Comments

-1

Please try autowiring as below and check

@Service
public class MyService {

    @Autowired
    private ARepository repository;

    // ...
}

4 Comments

I tried that (without final, because with final it needs to be set in the constructor), and it didn't work - the bean does not exist, and the dependency is unsatisfied. The error is still No qualifying bean of type 'com.mycompany..schema.domain_A.repository.ARepository'.
Remove the annotation @Repository from interface ARepository and try
Still no bean, sorry. :( It is creating an aEntityManagerFactory and aJpaConfig, but no TransactionManager and no Repositories.
Hold on - I set the option @Autowired(required = false), and now it's creating the Repository beans!! - but only after all other beans, so they are not injected into the services that need them. I'll edit the original question.
-1

Why not use Spring Profiles instead of configuring both database connections in a single application.yml file? You can create separate application.yml files for each profile and assign each profile to a different database connection.

By using profiles, you can easily manage different environments (e.g., development, testing, production) with their respective configurations. For example:

Create separate configuration files for each environment:

application-dev.yml for development application-prod.yml for production Activate the profile you need in your main application.yml or as a command-line argument when running your application.

Example: application.yml:

yaml spring: profiles: active: dev application-dev.yml:

yaml spring: datasource: url: jdbc:postgresql://localhost:5432/dev_db username: dev_user password: dev_password application-prod.yml:

yaml spring: datasource: url: jdbc:postgresql://localhost:5432/prod_db username: prod_user password: prod_password This way, you can cleanly separate your database configurations for each profile without cluttering a single file.

1 Comment

Can you please format your answer according to the community guidelines ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.