SpringBoot реализует динамическое переключение источников данных, что более элегантно!
SpringBoot реализует динамическое переключение источников данных, что более элегантно!

Путь к росту программиста

Интернет/Программист/Технологии/Обмен данными

Чтение этой статьи займет около 10 минут.

Откуда: juejin.cn/post/7261601725840179255

  • 1 Введение
  • 2 реализация кода
    • 2.1 Реализация ThreadLocal
    • 2.2 Реализация AbstractRoutingDataSource
    • 2.3 Настройка базы данных
    • 2.4 Тестирование
    • 2.5 Оптимизация и настройка

В последнее время при выполнении бизнес-требований мне нужно начинать с другой базы. Получить данные из данных и записать их в текущую базу данныхсередина,Поэтому возникает вопрос переключения источников данных.。Я думал использоватьMybatis-plusсерединапоставлятьиздинамический источник данныхSpringBootизstarter:dynamic-datasource-spring-boot-starterосознать。

После представления результатов выяснилось, что его нельзя использовать из-за экологических проблем в предыдущем проекте. Затем я изучил код переключения источника данных,Решите принять это самостоятельноThreadLocal+AbstractRoutingDataSourceмоделировать и реализовыватьdynamic-datasource-spring-boot-starterсерединанить Переключение источника данных。

1 Введение

упомянутый вышеThreadLocalиAbstractRoutingDataSource,Давайте кратко представим его.

ThreadLocal:Я уверен, что все с этим знакомы,полное имя:thread local variable。В основном для решения многихнить Проблемы несогласованности данных возникают из-за параллелизма。ThreadLocalдля каждогонить Предоставьте копию переменной,Убедитесь, что каждый нить не обращается к одному и тому же объекту в определенное время.,Этим достигается изоляция,увеличенная память,Но это сильно снижает расход производительности при синхронизации.,Уменьшает сложность управления параллелизмом.

  • Роль ThreadLocal: разделена в нити,Изоляция между разными нитями
  • Принцип ThreadLocal: когда ThreadLocal сохраняет значение,Получит текущий экземпляр нити в качестве ключа,Сохраняется на карте текущего объекта.

AbstractRoutingDataSource:По определению пользователяиз Выбор правила актуаленизисточник данных,

Роль: перед выполнением запроса,Установите используемый источник данных,Источник данных для реализации динамической маршрутизации,каждый разбаза данных Выполните его перед операцией запросаизабстрактныйметодdetermineCurrentLookupKey(),Решите, какой источник данных использовать.

2 реализация кода

Программная среда: SpringBoot2.4.8 Мибатис-плюс3.2.0 Друид1.2.6 Ломбок1.18.20 commons-lang3 3.10

2.1 Реализация ThreadLocal

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

Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 11:21
 **/
public class DataSourceContextHolder {
    //Этот класс не предоставляет никаких локальных переменных. Эти переменные отличаются от своего обычного соответствия тем, что каждая нить обращается к нити (через методы get и set) и имеет свою собственную независимую копию инициализированной переменной.
    private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();

    /**
     * Настроить источник данных
     * @param dataSourceName Имя источника данных
     */
    public static void setDataSource(String dataSourceName){
        DATASOURCE_HOLDER.set(dataSourceName);
    }

    /**
     * Получить текущий источник данных
     * @return Имя источника данных
     */
    public static String getDataSource(){
        return DATASOURCE_HOLDER.get();
    }

    /**
     * Удалить текущий источник данных
     */
    public static void removeDataSource(){
        DATASOURCE_HOLDER.remove();
    }

}

2.2 Реализация AbstractRoutingDataSource

Определить реализацию класса динамического источника данныхAbstractRoutingDataSource,проходитьdetermineCurrentLookupKeyметод Реализовано с помощью вышеперечисленногоизThreadLocalдобрыйсерединаизgetметод связывания,Достичь динамического переключения источников данных.

Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description: Реализация динамических источников данных и маршрутизация к различным источникам данных в соответствии с AbstractRoutingDataSource.
 * @date: 2023/7/27 11:18
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

В приведенном выше коде также реализован метод построения класса источника динамических данных, в основном для установки источника данных по умолчанию и различных целевых источников данных, сохраненных в Map. Ключом карты является заданное имя источника данных, а значением — соответствующий источник данных (DataSource).

2.3 Настройка базы данных

Настройте информацию о базе данных в application.yml:

Язык кода:javascript
копировать
#Настроить источник данных
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 15
      min-idle: 15
      max-active: 200
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: ""
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: false
      connection-properties: false
/**
 * @author: jiangjs
 * @description: Настроить источник данных
 * @date: 2023/7/27 11:34
 **/
@Configuration
public class DateSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource createDynamicDataSource(){
        Map<Object,Object> dataSourceMap = new HashMap<>();
        DataSource defaultDataSource = masterDataSource();
        dataSourceMap.put("master",defaultDataSource);
        dataSourceMap.put("slave",slaveDataSource());
        return new DynamicDataSource(defaultDataSource,dataSourceMap);
    }

}

Через класс конфигурации,Преобразование информации базы данных Конфигурации в файле Конфигурация в источник данных.,и добавлен вDynamicDataSourceсередина,в то же времяпроходить@BeanВоляDynamicDataSourceинъекцияSpringсередина Управлять,Позже при добавлении динамических источников данных,Будет использоваться.

2.4 Тестирование

В двух тестовых библиотеках — главной и подчиненной,Добавить таблицу отдельноtest_user,В нем только одно полеuser_name

Язык кода:javascript
копировать
create table test_user(
  user_name varchar(255) not null comment 'имя пользователя'
)

Добавьте информацию в основную библиотеку:

Язык кода:javascript
копировать
insert into test_user (user_name) value ('master');

Добавляем информацию из библиотеки:

Язык кода:javascript
копировать
insert into test_user (user_name) value ('slave');

Мы создаем метод getData, и параметром является имя источника данных, из которого необходимо запросить данные.

Язык кода:javascript
копировать
@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
    DataSourceContextHolder.setDataSource(datasourceName);
    TestUser testUser = testUserMapper.selectOne(null);
    DataSourceContextHolder.removeDataSource();
    return testUser.getUserName();
}

Вы можете реализовать другие классы Mapper и сущностей самостоятельно.

Результат выполнения:

1. При прохождении мастера:

картина

2. При прохождении раба:

картина

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

В приведенном выше коде,мы видимDataSourceContextHolder.setDataSource(datasourceName); Чтобы установить базу, которую текущий нит должен запросить данных,проходитьDataSourceContextHolder.removeDataSource(); чтобы удалить текущийнить Уже установленоизисточник данных。использовалMybatis-plusдинамический источник данныхиз Друзья,Еще следует помнить, что мы будем использовать его при переключении источников данных.DynamicDataSourceContextHolder.push(String ds); иDynamicDataSourceContextHolder.poll(); Глядя на исходный код этих двух методов, мы обнаружим, что стек фактически используется при использовании ThreadLocal. Преимущество этого метода в том, что он может использовать несколько вложенных источников данных. Я не буду рассказывать вам о реализации. друзья могут глянуть Mybatis - Исходный код динамических источников данных в плюсе.

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

Язык кода:javascript
копировать
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.5 Оптимизация и настройка

2.5.1 Источник данных переключения аннотаций

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

Говоря об этом, некоторые друзья, вероятно, подумают об использовании аннотаций для оптимизации. Давайте реализуем это дальше.

2.5.1.1 Определение аннотаций

Мы используем аннотацию динамического переключения источника данных mybatis: DS, код следующий:

Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 14:39
 **/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
    String value() default "master";
}
2.5.1.2 Реализация АОП
Язык кода:javascript
копировать
@Aspect
@Component
@Slf4j
public class DSAspect {

    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
    public void dynamicDataSource(){}

    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)){
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.removeDataSource();
        }
    }
}

В коде используется @Around,проходитьProceedingJoinPointполучатьаннотацияинформация,получить значение прохода аннотации,Затем установите текущий источник данных. Друзья, которые не знают об АОП, могут воспользоваться Google или Baidu.

2.5.1.3 Тестирование

Добавьте два метода тестирования:

Язык кода:javascript
копировать
@GetMapping("/getMasterData.do")
public String getMasterData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

Поскольку в @DS установлено значение по умолчанию: master, нет необходимости добавлять его при вызове основного источника данных.

Результат выполнения:

1. Вызовите метод getMasterData.do:

картина

2. Вызовите метод getSlaveData.do:

картина

По результатам выполнения мы также переключали источник данных через @DS, реализуя способ переключения источников данных через аннотации в Mybatis-plus с динамическим переключением источников данных.

2.5.2 Динамическое добавление источников данных

бизнес-сценарий : Иногда наш бизнес требует, чтобы мы добавляли эти источники данных из таблиц базы данных, в которых хранятся другие источники данных, а затем переключали эти источники данных в зависимости от различных ситуаций.

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

2.5.2.1 Объект источника данных
Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description: объект источника данных
 * @date: 2023/7/27 15:55
 **/
@Data
@Accessors(chain = true)
public class DataSourceEntity {

    /**
     * база данныхадрес     */
    private String url;
    /**
     * база данныхимя пользователя     */
    private String userName;
    /**
     * пароль
     */
    private String passWord;
    /**
     * база данныхводить машину     */
    private String driverClassName;
    /**
     * база данныеkey, который сохраняет ключ на карте
     */
    private String key;
}

Общая информация об источниках данных, определенных в сущностях,определитьkeyиспользуется какDynamicDataSourceсерединаMapсерединаизkey。

2.5.2.2 Изменение источника динамических данных
Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description: Реализация динамических источников данных и маршрутизация к различным источникам данных в соответствии с AbstractRoutingDataSource.
 * @date: 2023/7/27 11:18
 **/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    private final Map<Object,Object> targetDataSourceMap;

    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
        this.targetDataSourceMap = targetDataSources;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }

    /**
     * Добавить информацию об источнике данных
     * @param dataSources объект источника данныхсобирать     * @return Вернуться, чтобы добавить результаты
     */
    public void createDataSource(List<DataSourceEntity> dataSources){
        try {
            if (CollectionUtils.isNotEmpty(dataSources)){
                for (DataSourceEntity ds : dataSources) {
                    //проверятьбаза данных Является ли это возможнымсоединять                    Class.forName(ds.getDriverClassName());
                    DriverManager.getConnection(ds.getUrl(),ds.getUserName(),ds.getPassWord());
                    //Определяем источник данных
                    DruidDataSource dataSource = new DruidDataSource();
                    BeanUtils.copyProperties(ds,dataSource);
                    //При подаче заявки на соединение выполните validationQuery, чтобы проверить, действительно ли соединение. Рекомендуется, чтобы Конфигурация имела значение TRUE, чтобы полученное соединение не было недоступным.
                    dataSource.setTestOnBorrow(true);
                    //Рекомендуется указать значение «Конфигурация» true, что не влияет на производительность и обеспечивает безопасность.
                    //Обнаружение при подаче заявки на соединение. Если время простоя превышает timeBetweenEvictionRunsMillis, выполните validationQuery, чтобы проверить, действительно ли соединение.
                    dataSource.setTestWhileIdle(true);
                    //SQL используется для определения допустимости соединения, требованием является оператор запроса.
                    dataSource.setValidationQuery("select 1 ");
                    dataSource.init();
                    this.targetDataSourceMap.put(ds.getKey(),dataSource);
                }
                super.setTargetDataSources(this.targetDataSourceMap);
                // Поместите объединенную информацию в TargetDataSources в управление разрешенными источниками данных.
                super.afterPropertiesSet();
                return Boolean.TRUE;
            }
        }catch (ClassNotFoundException | SQLException e) {
            log.error("---Ошибка программы---:{}", e.getMessage());
        }
        return Boolean.FALSE;
    }

    /**
     * Проверьте, существует ли источник данных
     * @param key Ключ, сохраненный в источнике данных
     * @return Возвращаемый результат: true: существует, false: не существует.
     */
    public boolean existsDataSource(String key){
        return Objects.nonNull(this.targetDataSourceMap.get(key));
    }
}

после трансформацииизDynamicDataSourceсередина,Мы можем добавить private final Map<Object,Object> targetDataSourceMap,этотmap会在添加источник данныхиз Конфигурацияфайл ВолясоздаватьизMapисточник данныхинформацияпроходитьDynamicDataSourceМетод конструктора для начального присваивания,Прямо сейчас:DateSourceConfigдобрыйсерединаизcreateDynamicDataSource()методсередина。

в то же время我们在该добрыйсерединадобавилcreateDataSourceметод,Создать источник данных,и добавил на карту,Сновапроходитьsuper.setTargetDataSources(this.targetDataSourceMap) ;Переназначьте целевой источник данных.

2.5.2.3 Динамическое добавление источников данных

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

Определите таблицу базы данных в основной базе данных, чтобы сохранить информацию о базе данных.

Язык кода:javascript
копировать
create table test_db_info(
    id int auto_increment primary key not null comment 'Идентификатор первичного ключа',
    url varchar(255) not null comment 'база данныхURL',
    username varchar(255) not null comment 'имя пользователя',
    password varchar(255) not null comment 'пароль',
    driver_class_name varchar(255) not null comment 'база данныхводить машину'    name varchar(255) not null comment 'база данныхимя'
)

Для удобства вносим в базу предыдущую подчиненную библиотеку и модифицируем имя базы.

Язык кода:javascript
копировать
insert into test_db_info(url, username, password,driver_class_name, name)
value ('jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false',
       'root','123456','com.mysql.cj.jdbc.Driver','add_slave')

Ваши друзья могут добавлять сущности и преобразователи, соответствующие таблице базы данных.

Добавьте источник данных при запуске SpringBoot:

Язык кода:javascript
копировать
/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 16:56
 **/
@Component
public class LoadDataSourceRunner implements CommandLineRunner {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private TestDbInfoMapper testDbInfoMapper;
    @Override
    public void run(String... args) throws Exception {
        List<TestDbInfo> testDbInfos = testDbInfoMapper.selectList(null);
        if (CollectionUtils.isNotEmpty(testDbInfos)) {
            List<DataSourceEntity> ds = new ArrayList<>();
            for (TestDbInfo testDbInfo : testDbInfos) {
                DataSourceEntity sourceEntity = new DataSourceEntity();
                BeanUtils.copyProperties(testDbInfo,sourceEntity);
                sourceEntity.setKey(testDbInfo.getName());
                ds.add(sourceEntity);
            }
            dynamicDataSource.createDataSource(ds);
        }
    }
}

После вышеуказанного запуска SpringBoot,Данные в таблице базы данных добавлены в динамический источник данных.,Вызываем предыдущий тестовый метод,Воля Имя источника данных Передайте его как параметр, чтобы увидеть результаты выполнения.。

2.5.2.4 Тестирование

картина

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

Ладно, на сегодня это всё. Надеюсь, что моё общение даст вам более глубокое понимание метода динамического переключения источников данных.

Работа лучше тяжелой работы, но игра – это пустая трата времени. Давайте действовать, друзья.

github:github.com/lovejiashn/… [1]

<END>

Язык кода:javascript
копировать
Содержимое включает основы Java.、JavaWeb、Оптимизация производительности MySQL、JVM、Замок、Миллионы одновременных、очередь сообщений、Высокопроизводительное кэширование、отражение、Принцип пружинного семейного ведра、микросервисы、Zookeeper... и другие технологические стеки!
boy illustration
RasaGpt — платформа чат-ботов на основе Rasa и LLM.
boy illustration
Nomic Embed: воспроизводимая модель внедрения SOTA с открытым исходным кодом.
boy illustration
Улучшение YOLOv8: EMA основана на эффективном многомасштабном внимании, основанном на межпространственном обучении, и эффект лучше, чем у ECA, CBAM и CA. Малые цели имеют очевидные преимущества | ICASSP2023
boy illustration
Урок 1 серии Libtorch: Тензорная библиотека Silky C++
boy illustration
Руководство по локальному развертыванию Stable Diffusion: подробные шаги и анализ распространенных проблем
boy illustration
Полностью автоматический инструмент для работы с видео в один клик: VideoLingo
boy illustration
Улучшения оптимизации RT-DETR: облегченные улучшения магистрали | Support Paddle облегченный rtdetr-r18, rtdetr-r34, rtdetr-r50, rtdet
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | Деформируемое внимание с большим ядром (D-LKA Attention), большое ядро ​​​​свертки улучшает механизм внимания восприимчивых полей с различными функциями
boy illustration
Создано Datawhale: выпущено «Руководство по тонкой настройке развертывания большой модели GLM-4»!
boy illustration
7B превышает десятки миллиардов, aiXcoder-7B с открытым исходным кодом Пекинского университета — это самая мощная модель большого кода, лучший выбор для корпоративного развертывания.
boy illustration
Используйте модель Huggingface, чтобы заменить интерфейс внедрения OpenAI в китайской среде.
boy illustration
Оригинальные улучшения YOLOv8: несколько новых улучшений | Сохранение исходной информации — алгоритм отделяемой по глубине свертки (MDSConv) |
boy illustration
Второй пилот облачной разработки | Быстро поиграйте со средствами разработки на базе искусственного интеллекта
boy illustration
Бесшовная интеграция, мгновенный интеллект [1]: платформа больших моделей Dify-LLM, интеграция с нулевым кодированием и встраивание в сторонние системы, более 42 тысяч звезд, чтобы стать свидетелями эксклюзивных интеллектуальных решений.
boy illustration
Решенная Ошибка | Загрузка PyTorch медленная: TimeoutError: [Errno 110] При загрузке факела истекло время ожидания — Cat Head Tiger
boy illustration
Brother OCR, библиотека с открытым исходным кодом для Python, которая распознает коды проверки.
boy illustration
Новейшее подробное руководство по загрузке и использованию последней демонстрационной версии набора данных COCO.
boy illustration
Выпущен отчет о крупной модели финансовой отрасли за 2023 год | Полный текст включен в загрузку |
boy illustration
Обычные компьютеры также могут работать с большими моделями, и вы можете получить личного помощника с искусственным интеллектом за три шага | Руководство для начинающих по локальному развертыванию LLaMA-3
boy illustration
Одной статьи достаточно для анализа фактора транскрипции SCENIC на Python (4)
boy illustration
Бросая вызов ограничениям производительности небольших видеокарт, он научит вас запускать большие модели глубокого обучения с ограниченными ресурсами, а также предоставит полное руководство по оценке и эффективному использованию памяти графического процессора!
boy illustration
Команда Fudan NLP опубликовала 80-страничный обзор крупномасштабных модельных агентов, в котором в одной статье представлен обзор текущего состояния и будущего агентов ИИ.
boy illustration
[Эксклюзив] Вы должны знать о новой функции JetBrains 2024.1 «Полнострочное завершение кода», чтобы решить вашу путаницу!
boy illustration
Краткое изложение базовых знаний о регистрации изображений 1.0
boy illustration
Новейшее подробное руководство по установке и использованию библиотеки cv2 (OpenCV, opencv-python) в Python.
boy illustration
Легко создайте локальную базу знаний для крупных моделей на основе Ollama+AnythingLLM.
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание решения. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Одна статья поможет вам понять RAG (Retrival Enhanced Generation) | Введение в концепцию и теорию + практику работы с кодом (включая исходный код).
boy illustration
Эволюция архитектуры шлюза облачной разработки
boy illustration
Docker и Kubernetes [Разработка контейнерных приложений с помощью Python]