Рубрики
Без рубрики

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

Узнайте, как использовать Spring AbstractRoutingDataSource для динамического определения фактического источника данных на основе текущего контекста.

Автор оригинала: baeldung.

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

Мы определяем наш Маршрутизатор 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 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.

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

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 .