3

I am moving an existing Spring boot application from Multitenancy to Database per tenant model. Some common Entities are in the Master database, whereas others will be in their respective database. These classes have been implemented and worked fine. Source to get ma started is this: https://callistaenterprise.se/blogg/teknik/2020/10/03/multi-tenancy-with-spring-boot-part3/

Classes

  1. MasterPersistenceConfig.java (MasterDatabaseRepository below are the JPA repositories that are gonna be in the master database)
  2. TenantPersistenceConfig.java
  3. DynamicDataSourceBasedMultiTenantConnectionProvider.java
  4. CurrentTenantIdentifierResolverImpl.java
  5. application.yml

MasterPersistenceConfig.java (MasterDatabaseRepository below are the JPA repositories that are gonna be in the master database)

@Log4j2
@Configuration
@EnableJpaRepositories(
        basePackages = { "${multitenancy.base-package}" },
        entityManagerFactoryRef = "masterEntityManagerFactory",
        transactionManagerRef = "masterTransactionManager",
        includeFilters = {
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MasterDatabaseRepository.class)
        }
)
@EnableConfigurationProperties({DataSourceProperties.class, JpaProperties.class})
public class MasterPersistenceConfig {
    private final ConfigurableListableBeanFactory beanFactory;
    private final JpaProperties jpaProperties;
    private final String entityPackages;

    @Autowired
    public MasterPersistenceConfig(ConfigurableListableBeanFactory beanFactory,
                                   JpaProperties jpaProperties,
                                   @Value("${multitenancy.master.entityManager.packages}")
                                   String entityPackages) {
        this.beanFactory = beanFactory;
        this.jpaProperties = jpaProperties;
        this.entityPackages = entityPackages;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(
            @Qualifier("masterDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setPersistenceUnitName("master-persistence-unit");
        em.setPackagesToScan(entityPackages);
        em.setDataSource(dataSource);
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        Map<String, Object> properties = new HashMap<>(this.jpaProperties.getProperties());
        properties.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        properties.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(this.beanFactory));
        em.setJpaPropertyMap(properties);
        return em;
    }

    @Bean
    public JpaTransactionManager masterTransactionManager(
            @Qualifier("masterEntityManagerFactory") EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }
}


TenantPersistenceConfig.java (Using bean of DynamicDataSourceBasedMultiTenantConnectionProvider and CurrentTenantIdentifierResolverImpl to create entityManagerFactory)

@Log4j2
@Configuration
@EnableJpaRepositories(
        basePackages = {"${multitenancy.base-package}"},
        entityManagerFactoryRef = "tenantEntityManagerFactory",
        transactionManagerRef = "tenantTransactionManager",
        includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = JpaRepository.class),
        excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MasterDatabaseRepository.class)
)
@EnableConfigurationProperties(JpaProperties.class)
public class TenantPersistenceConfig {

    private final ConfigurableListableBeanFactory beanFactory;
    private final JpaProperties jpaProperties;
    private final String entityPackages;

    @Autowired
    public TenantPersistenceConfig(
            ConfigurableListableBeanFactory beanFactory,
            JpaProperties jpaProperties,
            @Value("${multitenancy.tenant.entityManager.packages}")
            String entityPackages) {
        this.beanFactory = beanFactory;
        this.jpaProperties = jpaProperties;
        this.entityPackages = entityPackages;
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean tenantEntityManagerFactory(
            @Qualifier("dynamicDataSourceBasedMultiTenantConnectionProvider") MultiTenantConnectionProvider connectionProvider,
            @Qualifier("currentTenantIdentifierResolver") CurrentTenantIdentifierResolver tenantResolver) {
        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setPersistenceUnitName("tenant-persistence-unit");
        emfBean.setPackagesToScan(entityPackages);
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        emfBean.setJpaVendorAdapter(vendorAdapter);
        Map<String, Object> properties = new HashMap<>(this.jpaProperties.getProperties());
        properties.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        properties.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(this.beanFactory));
        properties.put(AvailableSettings.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
        properties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
        properties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
        emfBean.setJpaPropertyMap(properties);
        return emfBean;
    }

    @Primary
    @Bean
    public JpaTransactionManager tenantTransactionManager(
            @Qualifier("tenantEntityManagerFactory") EntityManagerFactory emf) {
        JpaTransactionManager tenantTransactionManager = new JpaTransactionManager();
        tenantTransactionManager.setEntityManagerFactory(emf);
        return tenantTransactionManager;
    }

    @Bean
    public TenantDataSource tenantDataSource(@Qualifier("dynamicDataSourceBasedMultiTenantConnectionProvider") MultiTenantConnectionProvider connectionProvider,
                                             @Qualifier("currentTenantIdentifierResolver") CurrentTenantIdentifierResolver tenantResolver) {
        return new TenantDataSource(connectionProvider, tenantResolver);
    }

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplateRead(@Qualifier("tenantDataSource") TenantDataSource tenantDataSource) {
        return new NamedParameterJdbcTemplate(tenantDataSource);
    }

    @Bean
    @Primary
    public JdbcTemplate tenantJdbcTemplate(@Qualifier("tenantDataSource") TenantDataSource tenantDataSource) {
        return new JdbcTemplate(tenantDataSource);
    }
}

DynamicDataSourceBasedMultiTenantConnectionProvider (Uses TenantRepository, which MasterEntityManager manages)



@Log4j2
@Component
public class DynamicDataSourceBasedMultiTenantConnectionProvider
        extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

    private static final String TENANT_POOL_NAME_SUFFIX = "DataSource";

    @Autowired
    @Qualifier("masterDataSource")
    private DataSource masterDataSource;

    @Qualifier("masterDataSourceProperties")
    @Autowired
    private DataSourceProperties dataSourceProperties;
    @Autowired
    private TenantRepository masterTenantRepository;

    private Map<String, HikariDataSource> tenantDataSources = new HashMap<>();

    @Autowired
    private DbProperties properties;

    public DynamicDataSourceBasedMultiTenantConnectionProvider() {
    }

    public HikariDataSource getDataSource(String code) {
        // masterTenantRepository usages
    }

    @PostConstruct
    public Map<String, DataSource> getAll() {
        // masterTenantRepository usages
    }

    @Override
    protected HikariDataSource selectAnyDataSource() {
        return (HikariDataSource) masterDataSource;
    }

    @Override
    protected HikariDataSource selectDataSource(String tenantIdentifier) {
        return getDataSource(tenantIdentifier);
    }

    private HikariDataSource createAndConfigureDataSource(Tenant tenant) {
       // Datasource build and return
    }

    @Override
    public Connection getAnyConnection() throws SQLException {
        // get connection using datasource
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        // get connection using datasource
    }
}


CurrentTenantIdentifierResolverImpl (Uses TenantRepository, which MasterEntityManager manages)

@Log4j2
@Component("currentTenantIdentifierResolver")
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {

    @Autowired
    private TenantRepository tenantRepository;

    @Override
    public String resolveCurrentTenantIdentifier() {
        // tenantRepository usages
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

application.yml

multitenancy:
  base-package: packageX
  datasource-cache:
    maximumSize: 100
    expireAfterAccess: 1
  master:
    entityManager:
      packages: packageX
    datasource:
      url: jdbc:mysql://${db-ip}:3306/master
      username: root
      password: password
  tenant:
    entityManager:
      packages: packageX
    datasource:
      url-prefix: jdbc:mysql://${db-ip}:3306/
      username: root
      password: password
      hikari:
        maximumPoolSize: 2
        minimumIdle: 0
        idleTimeout: 30000
  readreplica:
    base-package: packageX.reports
    entityManager:
      packages: packageX.reports
    datasource:
      url-prefix: jdbc:mysql://${slave-db-ip}:3306/
      username: root
      password: password
      hikari:
        maximumPoolSize: 2
        minimumIdle: 0
        idleTimeout: 30000

Everything works fine until this point. Now we want to add another datasource that points to a another IP, i.e. Read replica. When I add another PersistenceConfig file such as above, the application fails to start. Below are a new class added. Stack trace is added below as well.

ReadReplicaPersistenceConfig.java

@Log4j2
@Configuration
@EnableJpaRepositories(
        basePackages = {"${multitenancy.readreplica.base-package}"},
        entityManagerFactoryRef = "readReplicaEntityManagerFactory",
        transactionManagerRef = "readReplicaTransactionManager",
        includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = ReportsRepository.class)
)
@EnableConfigurationProperties(JpaProperties.class)
public class ReadReplicaPersistenceConfig {

    private final ConfigurableListableBeanFactory beanFactory;
    private final JpaProperties jpaProperties;
    private final String entityPackages;

    @Autowired
    public ReadReplicaPersistenceConfig(
            ConfigurableListableBeanFactory beanFactory,
            JpaProperties jpaProperties,
            @Value("${multitenancy.readreplica.entityManager.packages}")
            String entityPackages) {
        this.beanFactory = beanFactory;
        this.jpaProperties = jpaProperties;
        this.entityPackages = entityPackages;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean readReplicaEntityManagerFactory(
            @Qualifier("dynamicDataSourceBasedMultiTenantConnectionProvider") MultiTenantConnectionProvider connectionProvider,
            @Qualifier("currentTenantIdentifierResolver") CurrentTenantIdentifierResolver tenantResolver) {
        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setPersistenceUnitName("read-db-persistence-unit");
        emfBean.setPackagesToScan(entityPackages);
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        emfBean.setJpaVendorAdapter(vendorAdapter);
        Map<String, Object> properties = new HashMap<>(this.jpaProperties.getProperties());
        properties.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        properties.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(this.beanFactory));
        properties.put(AvailableSettings.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
        properties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
        properties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
        emfBean.setJpaPropertyMap(properties);
        return emfBean;
    }

    @Bean
    public JpaTransactionManager readReplicaTransactionManager(
            @Qualifier("readReplicaEntityManagerFactory") EntityManagerFactory emf) {
        JpaTransactionManager readReplicaTransactionManager = new JpaTransactionManager();
        readReplicaTransactionManager.setEntityManagerFactory(emf);
        return readReplicaTransactionManager;
    }

    @Bean
    public ReadReplicaDataSource readReplicaDataSource(@Qualifier("dynamicDataSourceBasedMultiTenantConnectionProvider")
                                                       MultiTenantConnectionProvider connectionProvider,
                                                       @Qualifier("currentTenantIdentifierResolver")
                                                       CurrentTenantIdentifierResolver tenantResolver) {
        return new ReadReplicaDataSource(connectionProvider, tenantResolver);
    }

    @Bean
    @Primary
    public NamedParameterJdbcTemplate namedParameterJdbcTemplateRead(@Qualifier("readReplicaDataSource")
                                                                     ReadReplicaDataSource readReplicaDataSource) {
        return new NamedParameterJdbcTemplate(readReplicaDataSource);
    }

    @Bean
    public JdbcTemplate readReplicaJdbcTemplate(@Qualifier("readReplicaDataSource") ReadReplicaDataSource
                                                        readReplicaDataSource) {
        return new JdbcTemplate(readReplicaDataSource);
    }
}

Summary of the spring.log stacktrace is . Full stacktrace can be found here: https://justpaste.it/6jwhs

[13:20:23.143] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'springAsyncConfig' of type [packageX.config.async.SpringAsyncConfig$$EnhancerBySpringCGLIB$$38910a1d] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.185] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'org.springframework.retry.annotation.RetryConfiguration' of type [org.springframework.retry.annotation.RetryConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.245] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@70516d8' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.272] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#6c13eb04' of type [org.springframework.beans.factory.config.PropertiesFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.273] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#6c13eb04' of type [java.util.Properties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.276] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#18e33fe' of type [org.springframework.data.repository.core.support.PropertiesBasedNamedQueries] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.277] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#23ba6f2c' of type [org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.278] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#23ba6f2c' of type [org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.296] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties' of type [org.springframework.boot.autoconfigure.orm.jpa.JpaProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.297] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'tenantPersistenceConfig' of type [packageX.dbframework.hibernateconfig.TenantPersistenceConfig$$EnhancerBySpringCGLIB$$f1fe8a65] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.302] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterDataSourceConfiguration' of type [packageX.dbframework.datasource.MasterDataSourceConfiguration$$EnhancerBySpringCGLIB$$8117359e] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.317] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterDataSourceProperties' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.335] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterDataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.341] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker' of type [org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.349] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#72ffcc16' of type [org.springframework.beans.factory.config.PropertiesFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.349] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#72ffcc16' of type [java.util.Properties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.349] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#72313bde' of type [org.springframework.data.repository.core.support.PropertiesBasedNamedQueries] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.350] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#5f53b6b2' of type [org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.350] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#5f53b6b2' of type [org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.353] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterPersistenceConfig' of type [packageX.dbframework.hibernateconfig.MasterPersistenceConfig$$EnhancerBySpringCGLIB$$540470ad] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:23.494] [restartedMain] [] [] [] [o.h.j.i.u.LogHelper:31] [INFO] - HHH000204: Processing PersistenceUnitInfo [name: master-persistence-unit]
[13:20:23.530] [restartedMain] [] [] [] [o.h.Version:44] [INFO] - HHH000412: Hibernate ORM core version 5.4.27.Final
[13:20:23.638] [restartedMain] [] [] [] [o.h.a.c.Version:56] [INFO] - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
[13:20:23.735] [restartedMain] [] [] [] [c.z.h.HikariDataSource:110] [INFO] - masterDataSource - Starting...
[13:20:24.041] [restartedMain] [] [] [] [c.z.h.HikariDataSource:123] [INFO] - masterDataSource - Start completed.
[13:20:24.056] [restartedMain] [] [] [] [o.h.d.Dialect:175] [INFO] - HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
[13:20:24.809] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'packageX.entityListeners.SyncJobCandidateListener' of type [packageX.entityListeners.SyncJobCandidateListener] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:24.833] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'packageX.entityListeners.SyncJobListener' of type [packageX.entityListeners.SyncJobListener] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:27.154] [restartedMain] [] [] [] [o.h.e.t.j.p.i.JtaPlatformInitiator:52] [INFO] - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
[13:20:27.162] [restartedMain] [] [] [] [o.s.o.j.LocalContainerEntityManagerFactoryBean:437] [INFO] - Initialized JPA EntityManagerFactory for persistence unit 'master-persistence-unit'
[13:20:27.165] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterEntityManagerFactory' of type [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:27.168] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'masterEntityManagerFactory' of type [com.sun.proxy.$Proxy164] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:27.191] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean '(inner bean)#6ada8fd3' of type [com.sun.proxy.$Proxy166] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:27.201] [restartedMain] [] [] [] [o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:350] [INFO] - Bean 'readReplicaPersistenceConfig' of type [packageX.dbframework.hibernateconfig.ReadReplicaPersistenceConfig$$EnhancerBySpringCGLIB$$759c9f91] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[13:20:27.206] [restartedMain] [] [] [] [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext:596] [WARN] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'healthEndpointGroupsBeanPostProcessor' defined in class path resource [org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.class]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'metaDataSourceAdvisor': Cannot resolve reference to bean 'methodSecurityMetadataSource' while setting constructor argument; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through field 'customRelyingPartyRegistrationRepository'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customRelyingPartyRegistrationRepository': Unsatisfied dependency expressed through field 'samlRegistrationRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'samlRegistrationRepository' defined in packageX.auth.repository.SamlRegistrationRepository defined in @EnableJpaRepositories declared on TenantPersistenceConfig: Cannot create inner bean '(inner bean)#7a8d7a43' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#7a8d7a43': Cannot resolve reference to bean 'tenantEntityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tenantEntityManagerFactory' defined in class path resource [packageXdbframework/hibernateconfig/TenantPersistenceConfig.class]: Unsatisfied dependency expressed through method 'tenantEntityManagerFactory' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamicDataSourceBasedMultiTenantConnectionProvider': Unsatisfied dependency expressed through field 'masterTenantRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tenantRepository' defined in packageX.dbframework.datasource.TenantRepository defined in @EnableJpaRepositories declared on MasterPersistenceConfig: Cannot resolve reference to bean 'jpaMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'readReplicaEntityManagerFactory' defined in class path resource [packageXdbframework/hibernateconfig/ReadReplicaPersistenceConfig.class]: Unsatisfied dependency expressed through method 'readReplicaEntityManagerFactory' parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'currentTenantIdentifierResolver': Unsatisfied dependency expressed through field 'tenantRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'packageX.dbframework.datasource.TenantRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

But when I change readReplicaEntityManagerFactory and readReplicaTransactionManager in ReadReplicaPersistenceConfig to tenantEntityManagerFactory and tenantTransactionManager respectively, application starts. Although I have not verified the which database my application is making change to.

I'd appreciate any and all help. Thank you.

I tried using @Lazy, @DependsOn to resolve dependencies to avoid BeanCreationException, but not avail.

vvs14
  • 720
  • 8
  • 19
  • I too have a similar problem. Side question: What is the strategy that you've used to switch read-only query executions to the replica databases? Are you using explicit @TransactionManager annotation on the read-only service methods? – Chandan Hegde Jan 30 '23 at 12:38
  • BTW, have you checked this post: https://stackoverflow.com/questions/62032148/spring-boot-and-aws-rds-read-replica? – Chandan Hegde Jan 30 '23 at 12:39
  • We are using JdbcTemplate to write queries to read replica. So things are easier for us. – vvs14 Feb 02 '23 at 17:54

1 Answers1

0

To answer my question, I moved TenantRepository, used in DynamicDataSourceBasedMultiTenantConnectionProvider and CurrentTenantIdentifierResolverImpl, into service and lazy loaded that service into these classes, and the application started.

Something like this:

@Service
public class TenantService{
    @Autowired
    private TenantRepository tenantRepository;
}

public class DynamicDataSourceBasedMultiTenantConnectionProvider {
    @Autowired
    @Lazy
    private TenantService tenantService ;
}

public class CurrentTenantIdentifierResolverImpl {
    @Autowired
    @Lazy
    private TenantService tenantService ;
}
vvs14
  • 720
  • 8
  • 19