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 .