tags : Spring Boot
While the previous solution provides a detailed approach to achieve multi-tenancy with multiple data sources in Spring Boot, there are alternative approaches that can simplify the configuration. One such approach is using the AbstractRoutingDataSource
class provided by Spring Framework.
Here are the steps to configure additional tenants and their data sources using AbstractRoutingDataSource
:
-
Add Maven Dependencies: Add the necessary dependencies to your
pom.xml
file:spring-boot-starter-data-jpa
for Spring Data JPA support.postgresql
(or any other database driver) for the specific database you are using.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>
-
Create Tenant Configuration: Create a configuration class to hold the tenant-specific data source properties. Each tenant will have its own instance of this configuration class.
@ConfigurationProperties(prefix = "tenant") public class TenantProperties { private String url; private String username; private String password; // getters and setters }
-
Configure Data Sources: Create a bean for each tenant’s data source using the
TenantProperties
configuration class. UseDataSourceBuilder
to build the data source based on the properties.@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean @ConfigurationProperties(prefix = "tenant1.datasource") public TenantProperties tenant1Properties() { return new TenantProperties(); } @Bean @ConfigurationProperties(prefix = "tenant2.datasource") public TenantProperties tenant2Properties() { return new TenantProperties(); } @Bean public DataSource dataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("tenant1", DataSourceBuilder.create() .driverClassName(dataSourceProperties().getDriverClassName()) .url(tenant1Properties().getUrl()) .username(tenant1Properties().getUsername()) .password(tenant1Properties().getPassword()) .build()); targetDataSources.put("tenant2", DataSourceBuilder.create() .driverClassName(dataSourceProperties().getDriverClassName()) .url(tenant2Properties().getUrl()) .username(tenant2Properties().getUsername()) .password(tenant2Properties().getPassword()) .build()); AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { @Override protected Object determineCurrentLookupKey() { // Implement the logic to determine the current tenant identifier // This can be based on user authentication or any other logic return "tenant1"; } }; routingDataSource.setTargetDataSources(targetDataSources); routingDataSource.setDefaultTargetDataSource(targetDataSources.get("tenant1")); routingDataSource.afterPropertiesSet(); return routingDataSource; } }
-
Configure JPA and Hibernate: Configure JPA and Hibernate to use the multi-tenant data source. Set the
hibernate.multiTenancy
property toDATABASE
and provide theMultiTenantConnectionProvider
andCurrentTenantIdentifierResolver
beans.@Configuration public class JpaConfig { @Autowired private DataSource dataSource; @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.multiTenancy", MultiTenancyStrategy.DATABASE); properties.put("hibernate.multi_tenant_connection_provider", multiTenantConnectionProvider()); properties.put("hibernate.tenant_identifier_resolver", currentTenantIdentifierResolver()); LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); em.setPackagesToScan("com.example.domain"); em.setJpaPropertyMap(properties); return em; } @Bean public MultiTenantConnectionProvider multiTenantConnectionProvider() { return new DataSourceBasedMultiTenantConnectionProviderImpl(); } @Bean public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() { return new CurrentTenantIdentifierResolver() { @Override public String resolveCurrentTenantIdentifier() { // Implement the logic to determine the current tenant identifier // This can be based on user authentication or any other logic return "tenant1"; } @Override public boolean validateExistingCurrentSessions() { return false; } }; }
}```
https://www.baeldung.com/multitenancy-with-spring-data-jpa
when we perform any tenant operation, we need to know the tenant ID before creating any transaction. So, we need to set the tenant ID in a Filter or Interceptor before hitting controller endpoints.