Практика: Spring Multi-Tenant Data Source Management AbstractRoutingDataSource!

Spring Boot Spring Spring Cloud

图片

Оригинал: Miss Sister Taste (идентификатор публичной учетной записи WeChat: xjjdog), добро пожаловать, пожалуйста, сохраните источник для перепечатки.

Во многих случаях нам необходимо получить доступ к нескольким источникам данных в службе. Хотя это делает общий дизайн менее элегантным, в реальном мире это действительно необходимо. Например, ваша компания обслуживает двух относительно крупных клиентов, но вы хотите, чтобы у них был общий набор кодов.

То есть ваш код изначально не учитывал дизайн мультиарендности, а тут такая наболевшая необходимость. Но, к счастью, это не было взрывным ростом числа арендаторов.

Помимо введения некоторых компонентов подбазы данных и подтаблиц, Spring сам предоставляет метод AbstractRoutingDataSource, который делает возможным управление большинством источников данных. На самом деле ограничений на использование sub-database и sub-table компонентов много, надо сначала разобраться с этой горой дерьма, а потом уже терпеть жесткие требования middleware для своего SQL, наоборот, это какой-то дикий способ максимально изменить код.

Сердце хуже действия. Далее, давайте взглянем на его конкретную реализацию.

1. Основные принципы

Суть динамического переключения нескольких источников данных заключается в том, что нижний уровень Spring предоставляет класс AbstractRoutingDataSource для маршрутизации источников данных. AbstractRoutingDataSource реализует интерфейс DataSource, поэтому мы можем внедрить его непосредственно в свойства DataSource.

В основном мы наследуем этот класс и реализуем в нем метод defineCurrentLookupKey(), и этому методу нужно только вернуть имя базы данных.

Например, Контроллер распределяет бизнес-логику, получая значение, переданное внешним бизнесом. Он может вручную установить идентификатор базы данных для текущего запроса, а затем направить его к правильной библиотечной таблице.

@Controller
public class ARDTestController {
    @GetMapping("test")
    public void chifeng(){
        //db-a 应该是上层传递下来的属性,我们可以把它放在ThreadLocal里
        DataSourceContextHolder.setDbKey("db-a");
    }
}

Итак, когда оператор SQL выполняется, как он узнает, на какой источник данных ему нужно переключиться? Нужно ли постоянно передавать атрибут db-a?

В Java вы можете использовать ThreadLocal для привязки этого сквозного свойства. Принципы реализации, такие как вложенные транзакции Spring, также основаны на ThreadLocal. Итак, DataSourceContextHolder — это, по сути, класс, работающий с ThreadLocal.

public class DataSourceContextHolder {
    private static InheritableThreadLocal<String> dbKey = new InheritableThreadLocal<>();

    public static void setDbKey(String key){
        dbKey.set(key);
    }

    public static String getDbKey(){
        return dbKey.get();
    }
}

2. Код конфигурации

Во-первых, мы настроили формат файла конфигурации. Например, следующий код настраивает две базы данных, db-a и db-b.

multi:
  dbs:
    db-a:
      driver-class-name: org.h2.Driver
      url: jdbc:h2:mem:dba;MODE=MYSQL;DATABASE_TO_UPPER=false;
    db-b:
      driver-class-name: org.h2.Driver
      url: jdbc:h2:mem:dbb;MODE=MYSQL;DATABASE_TO_UPPER=false;

Затем мы анализируем его, называемый свойствами.

@ConfigurationProperties(prefix = "multi")
@Configuration
public class DbsProperties {
    private Map<String, Map<String, String>> dbs = new HashMap<>();

    public Map<String, Map<String, String>> getDbs() {
        return dbs;
    }

    public void setDbs(Map<String, Map<String, String>> dbs) {
        this.dbs = dbs;
    }
}

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

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDbKey();
    }
}

Последним шагом является установка источника данных по умолчанию для всего проекта. Обратите внимание, что после того, как мы сгенерируем DynamicDataSource, нам также необходимо предоставить значения свойств targetDataSource и defaultTargetDataSource для правильной работы.

@Configuration
public class DynamicDataSourceConfiguration {
    @Autowired
    DbsProperties properties;

    @Bean
    public DataSource dataSource(){
        DynamicDataSource dataSource = new DynamicDataSource();
        final Map<Object,Object> targetDataSource  = getTargetDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        //TODO 默认数据库需要设置
        dataSource.setDefaultTargetDataSource(targetDataSource.values().iterator().next());
        return dataSource;
    }

    private Map<Object,Object> getTargetDataSource(){
        Map<Object,Object> dataSources = new HashMap<>();
        this.properties.getDbs().entrySet().stream()
                .forEach(e->{
                    DriverManagerDataSource dmd = new DriverManagerDataSource();
                    dmd.setUrl(e.getValue().get("url"));
                    dmd.setDriverClassName(e.getValue().get("driver-class-name"));
                    dataSources.put(e.getKey(),dmd);
                });
        return  dataSources;
    }
}

3. Вопросы

С помощью приведенного выше простого кода можно реализовать простое управление несколькими источниками данных Spring. Но, очевидно, у него еще много проблем.

  1. Режим выбора дизайна продукта необходим для переключения бизнеса.

  2. Внешний интерфейс может использовать метод помещения его в localStroage для сохранения атрибута, а может использовать метод перехватчика для передачи переменной каждый раз.

  3. Каждый раз, когда бэкэнд запрашивает, вам нужно привести целевую базу данных, которую можно разместить в ThreadLocal. Однако у ThreadLocal есть проблема прозрачной передачи потоков: если в задаче открыты дочерние потоки, переменные не могут быть разделены.

  4. Поскольку таблица выбирается динамически, такие режимы, как автоматическое создание и обновление JPA, будут недоступны. Для тестирования и юнит-тестирования не удобно, при тестировании интерфейса тоже приходится каждый раз форсировать указанную библиотеку.

  5. Поскольку это режим изменения источника данных, каждый раз, когда вы добавляете библиотеку, вам нужно перезапустить ее и подключиться к сети. Уничтожение источника данных является проблемой, если он должен быть динамическим.

End

Для микросервиса существует множество стратегий ограничения по умолчанию, например, сервисы между разными доменами не могут совместно использовать базу данных. Эти базовые принципы являются базовыми принципами обновления микросервисов.

Точно так же, если мы добавим идентификатор поля арендатора в каждую таблицу в начале дизайна, это будет намного более плавно при написании кода. Но в мире не так уж много «если».

Почему существуют принципы? Конечно, это для людей, чтобы сломаться.

Программирование — это всего лишь инструмент, в любом случае код в ваших руках, а как играть — зависит от ваших потребностей и вашего настроения. Все дороги ведут в Рим, извилистые тропинки ведут в укромные места, а пейзаж бесконечно хорош.

Об авторе:Мисс сестра вкус(xjjdog), публичная учетная запись, которая не позволяет программистам идти в обход. Сосредоточьтесь на инфраструктуре и Linux. Десять лет архитектуры, десятки миллиардов ежедневного трафика, обсуждение с вами мира высокой параллелизма, дающие вам другой вкус. Мой личный WeChat xjjdog0, добро пожаловать в друзья для дальнейшего общения.

Рекомендуемое чтение:

1. Играйте в линукс
2. Альбом «Какой вкус»

3. блютуз как мечта
4. Убийство!
5. Отключил архитектора, оставил только скрипт
6. ОШИБКА, написанная архитектором, необычна

Мисс сестра вкус

Не завидуйте мандаринкам или бессмертным, просто долго корректируйте строчку кода

319 оригинальный контент