Example Explanation of Spring AbstractRoutingDatasource Dynamic Data Source

  • 2021-11-01 03:34:36
  • OfStack

Spring AbstractRoutingDatasource Dynamic Data Source

Data source context

AbstractRoutingDatasource needs information to decide which data source to route to, which we call context. You can market any object. In the following example, we use enumeration ClientDatabase as context:


public enum ClientDatabase {
    CLIENT_A, CLIENT_B
}

It is worth noting that in practice, context can be any meaningful domain object. As Environment defines a context in which enumerations can include PRODUCTION, DEVELOPMENT, and and TESTING.

Context Holder (ContextHolder)

A context holder implementation is a container for storing the current context, such as an ThreadLocal object. In addition to context references, you should also include set, get, clear static methods. AbstractRoutingDatasource will query ContextHolder for the context, and then use the context to find the actual data source.

The most important thing here is to use ThreadLocal, so that the context binding is only the current execution thread. This method is used to ensure that when data access logic uses transactions across multiple data sources:


public class ClientDatabaseContextHolder {
    private static ThreadLocal<ClientDatabase> CONTEXT  = new ThreadLocal<>();
    public static void set(ClientDatabase clientDatabase) {
        Assert.notNull(clientDatabase, "clientDatabase cannot be null");
        CONTEXT.set(clientDatabase);
    }
    public static ClientDatabase getClientDatabase() {
        return CONTEXT.get();
    }
    public static void clear() {
        CONTEXT.remove();
    }
}

Data source router

ClientDataSourceRouter inherits AbstractRoutingDataSource as defined below. Override the method determineCurrentLookupKey and query ClientDatabaseContextHolder for the current context. AbstractRoutingDataSource helps us deal with other logic, only to understand that it transparently returns the appropriate data source:


public class ClientDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return ClientDatabaseContextHolder.getClientDatabase();
    }
}

Configure

The DataSource object is stored using the Map context to configure AbstractRoutingDataSource. Of course, you can also specify a default data source, and select the default data source when no context is set.


@Configuration
public class RoutingTestConfiguration {
    @Bean
    public ClientService clientService() {
        return new ClientService(new ClientDao(clientDatasource()));
    }
 
    @Bean
    public DataSource clientDatasource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        DataSource clientADatasource = clientADatasource();
        DataSource clientBDatasource = clientBDatasource();
        targetDataSources.put(ClientDatabase.CLIENT_A, clientADatasource);
        targetDataSources.put(ClientDatabase.CLIENT_B, clientBDatasource);
        ClientDataSourceRouter clientRoutingDatasource  = new ClientDataSourceRouter();
        clientRoutingDatasource.setTargetDataSources(targetDataSources);
        clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);        
        return clientRoutingDatasource;
    }
    // ...
}

We can use data sources in different ways, but typical scenarios are created at runtime or found using JNDI. The following defines the sample Dao class, which relies on dynamic data sources for instantiation:


public class ClientDao {
    private static final String SQL_GET_CLIENT_NAME = "select name from client";
    private final JdbcTemplate jdbcTemplate;
    public ClientDao(DataSource datasource) {
        this.jdbcTemplate = new JdbcTemplate(datasource);
    }
    public String getClientName() {
        return this.jdbcTemplate.query(SQL_GET_CLIENT_NAME, rowMapper).get(0);
    }
    private static RowMapper<String> rowMapper = (rs, rowNum) -> {
        return rs.getString("name");
    };
}

Using dynamic data sources

In actual use, the context is set first, and then the business operation is performed. We define the business layer, in which the business method includes context as parameters. Set the context before executing the business and clear the context after the call:


public class ClientService {
    private final ClientDao clientDao;
    public ClientService(ClientDao clientDao) {
        this.clientDao = clientDao;
    }
    public String getClientName(ClientDatabase clientDb) {
        ClientDatabaseContextHolder.set(clientDb);
        String clientName = this.clientDao.getClientName();
        ClientDatabaseContextHolder.clear();
        return clientName;
    }
}

Summarize

This article explains how to use Spring AbstractRoutingDataSource to implement dynamic data source switching with examples.


Related articles: