A Guide to Spring AbstractRoutingDatasource

1. Overview

In this quick article, we’ll look at Spring’s
AbstractRoutingDatasource as a way of dynamically determining the
actual DataSource based on the current context
.

As a result, we’ll see that we can keep DataSource lookup logic out of
the data access code.

2. Maven Dependencies

Let’s start by declaring spring-context, spring-jdbc, spring-test, and
h2 as dependencies in the pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.8.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.195</version>
        <scope>test</scope>
    </dependency>
</dependencies>

The latest version of the dependencies can be found
here.

3. Datasource Context

AbstractRoutingDatasource requires information to know which actual
DataSource to route to. This information is typically referred to as a
Context.

While the Context used with AbstractRoutingDatasource can be any
Object, an enum is used for defining them. In our example, we’ll use
the notion of a ClientDatabase as our context with the following
implementation:

public enum ClientDatabase {
    CLIENT_A, CLIENT_B
}

Its worth noting that, in practice, the context can be whatever makes
sense for the domain in question.

For example, another common use case involves using the notion of an
Environment to define the context. In such a scenario, the context
could be an enum containing PRODUCTION, DEVELOPMENT, and TESTING.

4. Context Holder

The context holder implementation is a container that stores the current
context as a ThreadLocal reference.

In addition to holding the reference, it should contain static methods
for setting, getting, and clearing it. AbstractRoutingDatasource will
query the ContextHolder for the Context and will then use the context to
look up the actual DataSource.

It’s critically important to use ThreadLocal here so that the context
is bound to the currently executing thread
.

It’s essential to take this approach so that behavior is reliable when
data access logic spans multiple data sources and uses transactions:

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();
    }
}

5. Datasource Router

We define our ClientDataSourceRouter to extend the Spring
AbstractRoutingDataSource. We implement the necessary
determineCurrentLookupKey method to query our
ClientDatabaseContextHolder and return the appropriate key.

The AbstractRoutingDataSource implementation handles the rest of the
work for us and transparently returns the appropriate DataSource:

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return ClientDatabaseContextHolder.getClientDatabase();
    }
}

6. Configuration

We need a Map of contexts to DataSource objects to configure our
AbstractRoutingDataSource. We can also specify a default DataSource
to use if there is no context set.

The DataSources we use can come from anywhere but will typically be
either created at runtime or looked up using JNDI:

@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;
    }

    // ...
}

7. Usage

When using our AbstractRoutingDataSource, we first set the context and
then perform our operation. We make use of a service layer that takes
the context as a parameter and sets it before delegating to data-access
code and clearing the context after the call.

As an alternative to manually clearing the context within a service
method, the clearing logic can be handled by an AOP point cut.

It’s important to remember that the context is thread bound especially
if data access logic will be spanning multiple data sources and
transactions:

public class ClientService {

    private ClientDao clientDao;

    // standard constructors

    public String getClientName(ClientDatabase clientDb) {
        ClientDatabaseContextHolder.set(clientDb);
        String clientName = this.clientDao.getClientName();
        ClientDatabaseContextHolder.clear();
        return clientName;
    }
}

8. Conclusion

In this tutorial, we looked at the example how to use the Spring
AbstractRoutingDataSource. We implemented a solution using the notion
of a Client – where each client has its DataSource.

And, as always, examples can found
over
on GitHub
.

Leave a Reply

Your email address will not be published.