Je crée une application avec multitenancy dynamique. La base de données master contient une table avec des connexions aux dbs des locataires.
Everithing semble ok. Mais l'application de démarrage de printemps échoue en raison de:
***************************
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
j'ai marqué un haricot comme primaire, mais cela n'a pas aidé:
@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;
}
}
et locataires 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;
}
Il ressemble bug dans WebMvcAutoConfiguration $ EnableWebMvcConfiguration lorsqu'il ignore @Primary
La résolution était simple. J'ai marqué masterTransactionManager avec l'annotation @Primary mais pas le bean masterEntityManagerFactory. – coreer