Руководство по Spring AbstractRoutingDatasource

1. Обзор

В этой быстрой статье мы рассмотрим AbstractRoutingDatasource в Spring как способ динамического определения фактического DataSource на основе текущего контекста .

В результате мы увидим, что мы можем не допустить логику поиска DataSource в код доступа к данным.

2. Зависимости Maven

Начнем с объявления 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>

Последнюю версию зависимостей можно найти https://search.maven.org/classic/#search%7Cga%7C1%7C(g%3A%22org.springframework%22%20AND%20(a%3A%22spring- контекст% 22% 20OR% 20a% 3A% 22spring-22% JDBC% 20OR% 20a% 3A% 22spring-тест% 22))% 20OR% 20 (г% 3A% 22com.h2database% 22% 20and% 20a% 3A% 22h2% 22)[здесь].

3. Контекст источника данных

AbstractRoutingDatasource требуется информация, чтобы знать, к какому фактическому DataSource направить. Эта информация обычно называется Context.

Хотя Context , используемый с AbstractRoutingDatasource , может быть любым Object, enum используется для их определения. В нашем примере мы будем использовать понятие ClientDatabase в качестве нашего контекста со следующей реализацией:

public enum ClientDatabase {
    CLIENT__A, CLIENT__B
}

Стоит отметить, что на практике контекст может быть любым, что имеет смысл для рассматриваемой области.

Например, другой распространенный вариант использования включает в себя использование понятия Environment для определения контекста. В таком сценарии контекст может быть перечислением, содержащим PRODUCTION , DEVELOPMENT и TESTING .

4. Держатель контекста

Реализация держателя контекста - это контейнер, в котором текущий контекст хранится в виде ссылки:/java-threadlocal[ ThreadLocal ]ссылка.

Помимо хранения ссылки, она должна содержать статические методы для установки, получения и очистки. AbstractRoutingDatasource запросит ContextHolder для контекста и затем использует контекст для поиска фактического DataSource .

Здесь крайне важно использовать ThreadLocal , чтобы контекст был связан с текущим выполняющимся потоком .

Важно использовать этот подход, чтобы поведение было надежным, когда логика доступа к данным охватывает несколько источников данных и использует транзакции:

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. Маршрутизатор источника данных

Мы определяем наш ClientDataSourceRouter для расширения Spring AbstractRoutingDataSource . Мы реализуем необходимый метод determineCurrentLookupKey , чтобы запросить наш ClientDatabaseContextHolder и вернуть соответствующий ключ.

Реализация AbstractRoutingDataSource обрабатывает остальную часть работы за нас и прозрачно возвращает соответствующий DataSource:

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

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

6. Конфигурация

Нам нужно Map контекстов для DataSource объектов для настройки нашего AbstractRoutingDataSource . Мы также можем указать DataSource по умолчанию, который будет использоваться, если не задан контекст.

Используемые нами _DataSource _ могут поступать откуда угодно, но обычно они создаются либо во время выполнения, либо ищутся с использованием 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. Использование

При использовании нашего AbstractRoutingDataSource мы сначала устанавливаем контекст, а затем выполняем нашу операцию. Мы используем сервисный уровень, который принимает контекст в качестве параметра и устанавливает его перед делегированием к коду доступа к данным и очищает контекст после вызова.

В качестве альтернативы ручной очистке контекста в сервисном методе логика очистки может быть обработана срезом точек AOP.

Важно помнить, что контекст thread bound , особенно если логика доступа к данным будет охватывать несколько источников данных и транзакций:

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. Заключение

В этом руководстве мы рассмотрели пример использования Spring AbstractRoutingDataSource . Мы реализовали решение, используя понятие Client - где каждый клиент имеет свой DataSource .

И, как всегда, примеры можно найти over на GitHub .