Получить несколько источников данных SpringBoot (2): источники динамических данных

Spring Boot

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

1. Введение

Для обработки нескольких баз данных предыдущая статья "Получить несколько источников данных SpringBoot (1): стратегии с несколькими источниками》Было упомянуто, что существует несколько наборов источников данных, динамические источники данных, параметризованные источники данных и т. д. Эта статья является второй статьей: «Динамические источники данных». Динамические источники данных могут решить проблемы, заключающиеся в том, что обработка нескольких наборов источников данных недостаточно гибка и требует много ресурсов. Пользователи могут унифицировать операционную логику в соответствии с фактическими потребностями бизнеса, если им необходимо переключать источники данных для обработки. Что является динамическим, на самом деле время пакетного переключения источников данных может выбираться динамически, и переключение может выполняться там, где это необходимо.

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

в этой статьеобразец кода:https://github.com/mianshenglee/my-example/tree/master/multi-datasource, читатель может посмотреть его вместе.

2. Описание процесса источника динамических данных

Динамический источник данных Spring Boot по существу хранит несколько источников данных на карте.Когда источник данных необходимо использовать, источник данных извлекается из карты для обработки. В Spring были предоставлены абстрактные классыAbstractRoutingDataSourceдля достижения этой функции. Поэтому, когда мы реализуем динамический источник данных, нам нужно только наследовать его и реализовать собственную логику получения источника данных. Процесс источника динамических данных выглядит следующим образом:

动态数据源

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

3. Внедрите динамические источники данных

3.1 Описание и конфигурация источника данных

3.1.1 Описание структуры пакета

В этом примере в основном есть следующие пакеты:

├─annotation ---- // 自定义注解
├─aop ----------- // 切面
├─config -------- // 数据源配置
├─constants ----- // 常用注解
├─context ------- // 自定义上下文
├─controller ---- // 访问接口
├─entity -------- // 实体
├─mapper -------- // 数据库dao操作
├─service ------- // 服务类
└─vo ------------ // 视图返回数据

3.1.2 Конфигурация информации о соединении с базой данных

Файл конфигурации по умолчанию для Spring Boot:application.properties, поскольку есть две конфигурации базы данных, рекомендуется настроить базу данных независимо, поэтому добавьте файл конфигурацииjbdc.properties, добавьте следующую пользовательскую конфигурацию базы данных master-slave:

# master
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/mytest?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.master.username=root
spring.datasource.master.password=111111

# slave
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/my_test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.slave.username=root
spring.datasource.slave.password=111111

3.1.3 Конфигурация источника данных

Согласно информации о подключении, введите источник данных в Spring, добавьтеDynamicDataSourceConfigфайл, настроенный следующим образом:

@Configuration
@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
    @Bean(DataSourceConstants.DS_KEY_MASTER)
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(DataSourceConstants.DS_KEY_SLAVE)
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

Уведомление:

  • используется здесьPropertySourceуказать файл конфигурации,ConfigurationPropertiesУкажите префикс конфигурации источника данных
  • использоватьMapperScanУкажите пакет для автоматического внедрения соответствующего класса преобразователя.
  • Запишите константу источника данных вDataSourceConstantsв классе
  • Из этой конфигурации мы видим, что конфигурация SqlSessionFactory была стерта из кода, а SqlSessionFactory, автоматически настроенная Spring Boot, может использоваться напрямую без нашей собственной конфигурации.

3.2 Настройки источника динамических данных

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

3.2.1 Конфигурация источника динамических данных

(1) Добавить зависимость jdbc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

(2) Добавить динамический класс источника данных

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 此处暂时返回固定 master 数据源, 后面按动态策略修改
        return DataSourceConstants.DS_KEY_MASTER;
    }
}

Уведомление:

  • наследовать абстрактный классAbstractRoutingDataSource, необходимо реализовать методdetermineCurrentLookupKey, политика маршрутизации.
  • Стратегия динамической маршрутизации реализуется на следующем этапе, а текущая стратегия напрямую возвращает основной источник данных.

(3) Установите источник динамических данных в качестве основного источника данных.

в предыдущем файле конфигурации источника данныхDynamicDataSourceConfig, добавьте следующий код:

@Bean
@Primary
public DataSource dynamicDataSource() {
    Map<Object, Object> dataSourceMap = new HashMap<>(2);
    dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
    dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
    //设置动态数据源
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    dynamicDataSource.setTargetDataSources(dataSourceMap);
    dynamicDataSource.setDefaultTargetDataSource(masterDataSource());

    return dynamicDataSource;
}
  • Используйте карту, чтобы сохранить несколько источников данных и настроить их как объекты динамических источников данных.
  • Установите источник данных по умолчанию в качестве основного источника данных.
  • использовать аннотацииPrimaryПредпочитаю получать из динамических источников данных

В то же время необходимоDynamicDataSourceConfigв, исключеноDataSourceAutoConfiguration, иначе появитсяThe dependencies of some of the beans in the application context form a cycleошибка.

@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })

3.2.2 Динамический выбор источника данных

**(1) Контекст ключа источника данных **

Во фронте фиксируется стратегия маршрутизации источника данных, которая всегда возвращает мастер, что явно не то, что нам нужно. Мы хотим переключаться там, где нам это нужно, и когда мы этого хотим. Следовательно, должно быть место для динамического получения ключа источника данных (мы называем его контекстом).Для веб-приложений доступ осуществляется в единицах потоков, и более целесообразно использовать ThreadLocal, как показано ниже:

public class DynamicDataSourceContextHolder {
    /**
     * 动态数据源名称上下文
     */
    private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
    /**
     * 设置/切换数据源
     */
    public static void setContextKey(String key){
        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
    }
    /**
     * 获取数据源名称
     */
    public static String getContextKey(){
        String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
        return key == null?DataSourceConstants.DS_KEY_MASTER:key;
    }

    /**
     * 删除当前数据源名称
     */
    public static void removeContextKey(){
        DATASOURCE_CONTEXT_KEY_HOLDER.remove();
    }
  • Для хранения с помощью DATASOURCE_CONTEXT_KEY_HOLDER требуется использование ключа источника данных.

  • Когда getContextKey, если ключ пуст, он по умолчанию вернет master

(2) Установить динамические данныеDynamicDataSourceстратегия маршрутизации

Стратегия маршрутизации, которой нам нужно достичь, заключается в том, что, когда ключ источника данных установлен в контексте, ключ источника данных получается из контекста, чтобы знать, что нужно использовать соответствующий источник данных. Поэтому измените предыдущийDynamicDataSourceизdetermineCurrentLookupKeyМетоды, как показано ниже:

@Override
protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getContextKey();
}

3.2.3 Использование источника динамических данных

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

@RestController
@RequestMapping("/user")
public class TestUserController {

    @Autowired
    private TestUserMapper testUserMapper;

    /**
     * 查询全部
     */
    @GetMapping("/listall")
    public Object listAll() {
        int initSize = 2;
        Map<String, Object> result = new HashMap<>(initSize);
        //默认master查询
        QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
        List<TestUser> resultData = testUserMapper.selectAll(queryWrapper.isNotNull("name"));
        result.put(DataSourceConstants.DS_KEY_MASTER, resultData);

        //切换数据源,在slave查询
        DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_SLAVE);
        List<TestUser> resultDataSlave = testUserMapper.selectList(null);
        result.put(DataSourceConstants.DS_KEY_SLAVE, resultDataSlave);
        //恢复数据源
        DynamicDataSourceContextHolder.removeContextKey();
        //返回数据
        return ResponseResult.success(result);
    }

}
  • По умолчанию используется запрос источника основных данных.
  • Используйте setContextKey контекста, чтобы переключить источник данных, и используйте removeContextKey, чтобы восстановить его после использования.

3.3 Использование АОП для выбора источника данных

После приведенной выше конфигурации источника динамических данных может быть достигнуто динамическое переключение источников данных, но мы обнаружим, что при переключении источников данных нам нужно сделатьsetContextKeyиremoveContextKeyОперация, если вам нужно переключить больше методов, вы найдете много повторяющихся кодов. Как устранить эти повторяющиеся коды, вам нужно использовать динамические агенты. Если вы не понимаете динамические агенты, вы можете обратиться к моей статье "Знания, необходимые для разработки Java: динамический прокси". В Spring реализация АОП также основана на динамическом прокси. Здесь мы надеемся указать источник данных, требуемый функцией, с помощью аннотаций, чтобы исключить код шаблона продукта при переключении источника данных.

3.3.1 Определение аннотаций источника данных

существуетannotationpackage, добавить аннотации к источнику данныхDS, эту аннотацию можно написать в классе или в определении метода следующим образом:

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
    /**
     * 数据源名称
     */
    String value() default DataSourceConstants.DS_KEY_MASTER;
}

3.3.2 Определение аспектов источника данных

Определите аспект источника данных, который можно использовать дляDSАннотированные методы или классы для переключения источников данных.

(1) Добавить зависимости aop

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

(2) Определите срез

@Aspect
@Component
public class DynamicDataSourceAspect {
    @Pointcut("@annotation(me.mason.demo.dynamicdatasource.annotation.DS)")
    public void dataSourcePointCut(){

    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String dsKey = getDSAnnotation(joinPoint).value();
        DynamicDataSourceContextHolder.setContextKey(dsKey);
        try{
            return joinPoint.proceed();
        }finally {
            DynamicDataSourceContextHolder.removeContextKey();
        }
    }

    /**
     * 根据类或方法获取数据源注解
     */
    private DS getDSAnnotation(ProceedingJoinPoint joinPoint){
        Class<?> targetClass = joinPoint.getTarget().getClass();
        DS dsAnnotation = targetClass.getAnnotation(DS.class);
        // 先判断类的注解,再判断方法注解
        if(Objects.nonNull(dsAnnotation)){
            return dsAnnotation;
        }else{
            MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
            return methodSignature.getMethod().getAnnotation(DS.class);
        }
    }
}
  • Аннотация Pointcut используетannotationуказать аннотацию
  • Annotations Around используют обработку окружающих уведомлений, используют контекст для сопряжения с помощью аннотацийDSЗначение источника данных переключается, и после обработки источник данных восстанавливается.

3.3.3 Переключение источника данных с помощью AOP

На сервисном уровне мы определяемTestUserService, в нем есть два метода, которые должны получить данные от мастера и слейва, используя аннотацииDS,следующее:

/**
 * 查询master库User
 */
@DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser(){
    QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
    return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}

/**
 * 查询slave库User
 */
@DS(DataSourceConstants.DS_KEY_SLAVE)
public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); }

После этого определения обработка на уровне контроллера может стать:

@GetMapping("/listall")
public Object listAll() {
	int initSize = 2;
	Map<String, Object> result = new HashMap<>(initSize);
	//默认master数据源查询
	List<TestUser> masterUser = testUserService.getMasterUser();
	result.put(DataSourceConstants.DS_KEY_MASTER, masterUser);
	//从slave数据源查询
	List<TestUser> slaveUser = testUserService.getSlaveUser();
	result.put(DataSourceConstants.DS_KEY_SLAVE, slaveUser);
	//返回数据
	return ResponseResult.success(result);
}

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

4. Подумайте еще раз

После приведенного выше объяснения динамического источника данных и выбора источника данных АОП мы видим, что динамический источник данных очень гибкий.Если вы хотите переключиться, вам нужно только установить источник данных в контексте, или вы можете напрямую использовать аннотации в методы или классы для завершения. Теперь мы вручную кодируем, чтобы добиться, собственно, для MyBatis Plus, он также обеспечиваетПлагины для динамических источников данных, и заинтересованные друзья также могут использовать его экспериментально в соответствии с его официальной документацией.

Что еще необходимо рассмотреть или улучшить для динамических источников данных?

  • Как обрабатывается транзакция? На самом деле при разработке следует максимально избегать транзакций между базами данных, но если этого нельзя избежать, следует использовать распределенные транзакции.
  • Для текущего динамического источника данных, условно говоря, это по-прежнему фиксированный источник данных (такой как один ведущий и один ведомый, один ведущий и несколько ведомых и т. д.), то есть количество баз данных, которое было определено при кодировании, но только тогда, когда какой из них используется, динамически обрабатывается. Что делать, если сам источник данных не определен или вам необходимо подключиться к базе данных в соответствии с пользовательским вводом? Такая ситуация чаще возникает при управлении несколькими базами данных. Эту ситуацию, о которой я расскажу в следующей статье, я называю «параметрическими источниками данных об изменении».

5. Резюме

В этой статье объясняется реализация динамических источников данных, в основном настройка, реализация и использование динамических источников данных.Кроме того, АОП используется для устранения кода шаблона при переключении источников данных, чтобы мы могли сосредоточиться на разработке бизнес-кода и наконец, к источнику динамических данных. Я надеюсь, что вы сможете освоить обработку динамических источников данных.

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

использованная литература

Прошлые статьи

Мой официальный аккаунт (поискMason技术记录) для более технических записей:

mason