简体   繁体   English

当WebMvcAutoConfiguration $ EnableWebMvcConfiguration仅需要一个entityManager时,如何在Spring Boot中进行多租户

[英]How to make multitenancy in Spring Boot when WebMvcAutoConfiguration$EnableWebMvcConfiguration needs only one entityManager

I'm creating application with dynamic multitenancy. 我正在创建具有动态多租户的应用程序。 Master database contains table with connections to tenants' dbs. 主数据库包含与租户数据库连接的表。

Everithing looks ok. 一切都很好。 But spring boot application fails because of: 但是由于以下原因,Spring Boot应用程序失败:

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

Description:

Method requestMappingHandlerMapping in org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration required a single bean, but 2 were found:
    - masterEntityManagerFactory: defined by method 'masterEntityManagerFactory' in class path resource [com/dimanex/api/config/MasterDatabaseConfig.class]
    - tenantEntityManagerFactory: defined by method 'tenantEntityManagerFactory' in class path resource [com/dimanex/api/config/TenantsDatabaseConfig.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed


Process finished with exit code 1

I marked one bean as primary but this didn't help: 我将一个bean标记为主要,但这没有帮助:

@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@EnableJpaRepositories(
    entityManagerFactoryRef = MasterDatabaseConfig.MASTER_ENTITY_MANAGER_FACTORY_NAME,
    transactionManagerRef = MasterDatabaseConfig.MASTER_TRANSACTION_MANAGER_NAME,
    basePackages = {"com.dimanex.api.repository.master"})
@EnableTransactionManagement
public class MasterDatabaseConfig {

    public static final String MASTER_ENTITY_MANAGER_FACTORY_NAME = "masterEntityManagerFactory";
    public static final String MASTER_TRANSACTION_MANAGER_NAME = "masterTransactionManager";

    @Bean(destroyMethod = "close")
    public DataSource masterDataSource(@Value("${spring.datasource.url}") String url,
                                       @Value("${spring.datasource.dataSourceClassName}") String dataSourceClassName,
                                       @Value("${spring.datasource.username}") String user,
                                       @Value("${spring.datasource.password}") String password) {

        log.debug("Configuring datasource {} {} {}", dataSourceClassName, url, user);
        HikariConfig config = new HikariConfig();
        config.setDataSourceClassName(dataSourceClassName);
        config.addDataSourceProperty("url", url);
        config.addDataSourceProperty("user", user);
        config.addDataSourceProperty("password", password);
        return new HikariDataSource(config);
    }

    @Bean(name = MASTER_ENTITY_MANAGER_FACTORY_NAME)
    public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(DataSource masterDataSource,
                                                                             JpaProperties jpaProperties) {
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(masterDataSource);
        em.setPackagesToScan(new String[]{MasterBaseObject.class.getPackage().getName()});
        em.setJpaVendorAdapter(vendorAdapter);

        em.setJpaProperties(new Properties(){{
            final Properties self = this;
            jpaProperties.getHibernateProperties(masterDataSource).forEach((k, v) -> self.setProperty(k, v));
        }});

        em.setPersistenceUnitName("master");

        return em;
    }

    @Bean(name = MASTER_TRANSACTION_MANAGER_NAME)
    @Primary
    public JpaTransactionManager masterTransactionManager(@Qualifier(MASTER_ENTITY_MANAGER_FACTORY_NAME) EntityManagerFactory masterEntityManagerFactory){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(masterEntityManagerFactory);
        return transactionManager;
    }

}

And tenants configs: 和租户配置:

@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@EnableJpaRepositories(
    entityManagerFactoryRef = TenantsDatabaseConfig.TENANT_ENTITY_MANAGER_FACTORY_NAME,
    transactionManagerRef = TenantsDatabaseConfig.TENANT_TRANSACTION_MANAGER_NAME,
    basePackages = {"com.dimanex.api.repository.tenant"})
@EnableTransactionManagement
public class TenantsDatabaseConfig {

    public static final String TENANT_ENTITY_MANAGER_FACTORY_NAME = "tenantEntityManagerFactory";
    public static final String TENANT_TRANSACTION_MANAGER_NAME = "tenantsTransactionManager";

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Bean
    public MultiTenantConnectionProvider multiTenantConnectionProvider(@Value("${spring.datasource.dataSourceClassName}") String dataSourceClassName) {
        return new DimanexMultiTenantConnectionProvider(dataSourceClassName);
    }

    @Bean
    public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
        return new DimanexCurrentTenantResolver();
    }

    @Bean(name = TENANT_ENTITY_MANAGER_FACTORY_NAME)
    public LocalContainerEntityManagerFactoryBean tenantEntityManagerFactory(@Value("${spring.jpa.properties.hibernate.dialect}") String hibernateDialect,
                                                                             DataSource masterDataSource,
                                                                             MultiTenantConnectionProvider connectionProvider,
                                                                             CurrentTenantIdentifierResolver tenantResolver) {

        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setPackagesToScan(TenantBaseObject.class.getPackage().getName());
        emfBean.setJpaVendorAdapter(jpaVendorAdapter());
        emfBean.setDataSource(masterDataSource);
        Map<String, Object> properties = new HashMap<>();
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
        properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);

        properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
        properties.put("hibernate.dialect", hibernateDialect);

        emfBean.setJpaPropertyMap(properties);

        return emfBean;
    }

    @Bean(name = TENANT_TRANSACTION_MANAGER_NAME)
    public JpaTransactionManager tenantsTransactionManager(@Qualifier(TENANT_ENTITY_MANAGER_FACTORY_NAME) EntityManagerFactory tenantEntityManager) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(tenantEntityManager);
        return transactionManager;
    }

It looks like bug in WebMvcAutoConfiguration$EnableWebMvcConfiguration when it ignores @Primary 忽略@Primary时,它看起来像WebMvcAutoConfiguration $ EnableWebMvcConfiguration中的错误

Resolution was simple. 解决方法很简单。 I marked masterTransactionManager with @Primary annotation but not masterEntityManagerFactory bean. 我用@Primary批注标记了masterTransactionManager,但没有标记masterEntityManagerFactory bean。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM