Spring Framework Tutorial
Software Setup and Configuration (STS/Eclipse/IntelliJ)
Core Spring
Spring Annotations
Spring Data
Spring JDBC
Spring Security
AbstractRoutingDataSource
is an extremely useful part of the Spring Framework for situations where you need to determine the data source for a JDBC connection at runtime. The most common use case for AbstractRoutingDataSource
is multi-tenancy applications where the tenant or schema is determined on-the-fly based on some attribute (e.g., subdomain, a specific header, or a tenant identifier in a JWT token).
AbstractRoutingDataSource
is abstract, so you need to provide an implementation of the method determineCurrentLookupKey()
. This method determines which data source key to use.
Here's an example of how you can implement a simple AbstractRoutingDataSource
:
public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return TenantContext.getCurrentTenant(); // TenantContext is a hypothetical class. You need to implement this. } }
In the above example, TenantContext
might be a thread-local storage of the current tenant.
To use AbstractRoutingDataSource
, you'll typically set up multiple data sources (one for each tenant) and then a "routing" data source that will pick the right one:
@Configuration public class DataSourceConfig { @Bean public DataSource dataSource() { TenantAwareRoutingDataSource routingDataSource = new TenantAwareRoutingDataSource(); Map<Object, Object> dataSources = new HashMap<>(); dataSources.put("tenant1", tenant1DataSource()); dataSources.put("tenant2", tenant2DataSource()); routingDataSource.setTargetDataSources(dataSources); routingDataSource.setDefaultTargetDataSource(tenant1DataSource()); // default or fallback return routingDataSource; } public DataSource tenant1DataSource() { // create and return the data source for tenant 1 } public DataSource tenant2DataSource() { // create and return the data source for tenant 2 } }
The key thing here is the setTargetDataSources
method which sets a map of keys (tenant identifiers) to data sources. The determineCurrentLookupKey
method in your AbstractRoutingDataSource
subclass will determine which key (and thus which data source) to use for each transaction.
Transactions: If you're using Spring's declarative transaction management, you should ensure that determineCurrentLookupKey
provides consistent results throughout the transaction. If it can change during a transaction, you could accidentally mix operations on multiple databases in a single transaction.
Thread Safety: If you're storing the current tenant in a thread-local variable (a common approach), ensure you manage this carefully. Thread-local storage should be cleared after each request to prevent memory leaks or incorrect behavior if threads are reused (as in many servlet containers).
Connection Pools: Each tenant data source might have its own connection pool. This could lead to a lot of connections if you have many tenants, so it's essential to tune and monitor your connection pools.
Fallback: Setting a default target data source is useful as a fallback in case no key can be determined.
Dynamic Tenants: If tenants can be added at runtime, you'll need additional logic to add new data sources to the routing data source dynamically. This could involve some form of locking or synchronization.
In conclusion, AbstractRoutingDataSource
is a powerful tool for multi-tenancy scenarios, but it requires careful configuration and management.
Custom DataSource routing in Spring using AbstractRoutingDataSource:
AbstractRoutingDataSource
to define your own logic for determining which data source to use.public class CustomRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // Your custom logic to determine the current data source key return TenantContext.getCurrentTenant(); } }
Spring AbstractRoutingDataSource example:
AbstractRoutingDataSource
implementation:public class MyRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // Your logic to determine the current data source key return DynamicDataSourceContextHolder.getDataSourceKey(); } }
Configuring multiple DataSources with AbstractRoutingDataSource:
AbstractRoutingDataSource
to dynamically switch between them.<!-- Configuring AbstractRoutingDataSource in XML --> <bean id="routingDataSource" class="com.example.MyRoutingDataSource"> <property name="targetDataSources"> <map> <entry key="dataSource1" value-ref="dataSource1"/> <entry key="dataSource2" value-ref="dataSource2"/> </map> </property> </bean>
Dynamic DataSource switching in Spring with AbstractRoutingDataSource:
// Setting the current data source key DynamicDataSourceContextHolder.setDataSourceKey("dataSource1"); // Performing operations that will use dataSource1 // Switching to another data source DynamicDataSourceContextHolder.setDataSourceKey("dataSource2"); // Performing operations that will use dataSource2
Ensure that DynamicDataSourceContextHolder
is managing the current data source key in a thread-safe manner.