1. Обзор
В этой краткой статье мы рассмотрим Spring AbstractRoutingDataSource как способ динамического определения фактического источника данных на основе текущего контекста .
В результате мы увидим, что можем исключить логику DataSource lookup из кода доступа к данным.
2. Зависимости Maven
Давайте начнем с объявления spring-context, spring-jdbc, spring-test, и h2 в качестве зависимостей в pom.xml :
org.springframework spring-context 4.3.8.RELEASE org.springframework spring-jdbc 4.3.8.RELEASE org.springframework spring-test 4.3.8.RELEASE test com.h2database h2 1.4.195 test
Последнюю версию зависимостей можно найти здесь .
3. Контекст источника данных
AbstractRoutingDataSource требует информации, чтобы знать, к какому фактическому Источнику данных следует направить маршрут. Эта информация обычно называется контекстом .
В то время как Контекст , используемый с AbstractRoutingDataSource , может быть любым объектом, перечисление используется для их определения. В нашем примере мы будем использовать понятие Клиентская база данных как наш контекст со следующей реализацией:
public enum ClientDatabase { CLIENT_A, CLIENT_B }
Стоит отметить, что на практике контекст может быть любым, что имеет смысл для рассматриваемой области.
Например, другой распространенный вариант использования включает использование понятия Environment для определения контекста. В таком сценарии контекстом может быть перечисление, содержащее PRODUCTION , DEVELOPMENT и TESTING .
4. Держатель контекста
Реализация держателя контекста-это контейнер, в котором текущий контекст хранится в виде локальной ссылки на поток.
В дополнение к удержанию ссылки, она должна содержать статические методы для ее установки, получения и очистки. AbstractRoutingDataSource запросит у контекстхолдера Контекст, а затем будет использовать контекст для поиска фактического источника данных .
Критически важно использовать ThreadLocal здесь, чтобы контекст был привязан к текущему исполняемому потоку .
Важно использовать этот подход, чтобы поведение было надежным, когда логика доступа к данным охватывает несколько источников данных и использует транзакции:
public class ClientDatabaseContextHolder { private static ThreadLocalCONTEXT = 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. Маршрутизатор источника данных
Мы определяем наш Маршрутизатор ClientDataSource для расширения Spring AbstractRoutingDataSource . Мы реализуем необходимый метод determineCurrentLookupKey для запроса нашего клиентского DatabaseContextHolder и возвращаем соответствующий ключ.
Реализация AbstractRoutingDataSource обрабатывает остальную часть работы за нас и прозрачно возвращает соответствующий Источник данных:
public class ClientDataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return ClientDatabaseContextHolder.getClientDatabase(); } }
6. Конфигурация
Нам нужна Карта контекстов для источника данных объектов для настройки нашего AbstractRoutingDataSource . Мы также можем указать источник данных по умолчанию для использования, если контекст не задан.
Источник данных s, который мы используем, может быть получен из любого места, но обычно он либо создается во время выполнения, либо просматривается с помощью JNDI:
@Configuration public class RoutingTestConfiguration { @Bean public ClientService clientService() { return new ClientService(new ClientDao(clientDatasource())); } @Bean public DataSource clientDatasource() { Map
7. Использование
При использовании нашего AbstractRoutingDataSource мы сначала устанавливаем контекст , а затем выполняем нашу операцию. Мы используем уровень обслуживания, который принимает контекст в качестве параметра и устанавливает его перед делегированием коду доступа к данным и очисткой контекста после вызова.
В качестве альтернативы ручной очистке контекста в методе обслуживания логика очистки может обрабатываться с помощью отрезка точки AOP.
Важно помнить, что контекст привязан к потоку , особенно если логика доступа к данным будет охватывать несколько источников данных и транзакций:
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 . Мы реализовали решение, используя понятие Клиент – где у каждого клиента есть свой источник данных .
И, как всегда, примеры можно найти на GitHub .