A Guide to Spring AbstractRoutingDatasource – Spring AbstractRoutingDatasource指南

最后修改: 2017年 11月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

在这篇快速文章中,我们将看看Spring的AbstractRoutingDatasource,作为一种根据当前上下文动态确定实际DataSource的方式

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

因此,我们将看到,我们可以将DataSource查找逻辑从数据访问代码中剔除。

2. Maven Dependencies

2.Maven的依赖性

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

让我们先把spring-context、spring-jdbc、spring-test和h2作为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.

The latest version of the dependencies can be found here.

If you are using Spring Boot, we can use the starters for Spring Data and Test:

如果你使用的是Spring Boot,我们可以使用Spring Data和Test的启动器。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.195</version>
        <scope>test</scope>
    </dependency>
</dependencies>

3. Datasource Context

3.数据源背景

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

AbstractRoutingDatasource需要信息来知道要路由到哪个实际的DataSource。该信息通常被称为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:

虽然与 AbstractRoutingDatasource 一起使用的 Context 可以是任何 Object,但 enum 被用来定义它们。在我们的例子中,我们将使用ClientDatabase的概念作为我们的上下文,实现如下。

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.

例如,另一个常见的用例是使用Environment的概念来定义上下文。在这种情况下,上下文可以是一个包含PRODUCTIONDEVELOPMENTTESTING的枚举。

4. Context Holder

4.语料库

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

上下文持有者的实现是一个容器,它将当前的上下文存储为一个ThreadLocal引用。

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.

除了持有该引用外,它还应该包含用于设置、获取和清除该引用的静态方法。AbstractRoutingDatasource将查询ContextHolder的Context,然后使用该context来查找实际的DataSource

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

在这里使用ThreadLocal是非常重要的,这样上下文就会被绑定在当前执行的线程上。

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

5.数据源路由器

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

我们定义了我们的ClientDataSourceRouter以扩展Spring的AbstractRoutingDataSource。我们实现必要的determineCurrentLookupKey方法,以查询我们的ClientDatabaseContextHolder并返回适当的键。

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

AbstractRoutingDataSource实现为我们处理其余的工作,并透明地返回适当的DataSource:

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

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

6. Configuration

6.配置

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.

我们需要一个从上下文到DataSource对象的Map来配置我们的AbstractRoutingDataSource。我们还可以指定一个默认的DataSource,以便在没有设置上下文时使用。

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

我们使用的DataSources可以来自任何地方,但通常是在运行时创建或使用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;
    }

    // ...
}

When using Spring Boot, you can configure the DataSources in the application.properties file (i.e. ClientA ClientB):

使用Spring Boot时,你可以在application.properties文件中配置DataSources(即ClientA&ClientB)。

#database details for CLIENT_A
client-a.datasource.name=CLIENT_A
client-a.datasource.script=SOME_SCRIPT.sql

#database details for CLIENT_B
client-b.datasource.name=CLIENT_B
client-b.datasource.script=SOME_SCRIPT.sql

You can then create POJOs that will hold  the properties for your DataSources:

然后你可以创建POJO,为你的DataSources保存属性。

@Component
@ConfigurationProperties(prefix = "client-a.datasource")
public class ClientADetails {

    private String name;
    private String script;

    // Getters & Setters
}

and use them to construct your data source beans:

并使用它们来构建你的数据源Bean。

@Autowired
private ClientADetails clientADetails;
@Autowired
private ClientBDetails clientBDetails;

private DataSource clientADatasource() {
EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();
return dbBuilder.setType(EmbeddedDatabaseType.H2)
.setName(clientADetails.getName())
.addScript(clientADetails.getScript())
.build();
}

private DataSource clientBDatasource() {
EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();
return dbBuilder.setType(EmbeddedDatabaseType.H2)
.setName(clientBDetails.getName())
.addScript(clientBDetails.getScript())
.build();
}

7. Usage

7.使用方法

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.

在使用我们的AbstractRoutingDataSource时,我们首先设置上下文,然后执行我们的操作。我们利用一个服务层,将上下文作为一个参数,并在委托给数据访问代码之前将其设置,并在调用后清除上下文。

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

作为在服务方法中手动清除上下文的替代方法,清除逻辑可以由AOP点切割处理。

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

8.结论

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.

在本教程中,我们看了如何使用Spring AbstractRoutingDataSource的例子。我们使用Client的概念实现了一个解决方案–每个客户端都有其DataSource

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

而且,像往常一样,可以在GitHub上找到的例子