• Java 动态数据源配置


    目录

    一、动态数据源介绍

    1. AbstractRoutingDataSource

    2. 实现逻辑

    二、源代码

    1. 修改配置文件类

    2. 创建数据源枚举

    3. 数据源切换处理

    4. 继承AbstractRoutingDataSource

    5. 注入数据源

    6. 自定义多数据源切换注解

    7. AOP拦截类的实现

    8. 使用切换数据源注解

    9. 在启动项目的过程中会发生循环依赖的问题,直接修改启动类即可

    10. 启动成功


    一、动态数据源介绍

    1. AbstractRoutingDataSource

    DataSource是和线程绑定的,动态数据源的配置主要是通过继承AbstractRoutingDataSource类实现的,

    抽象类AbstractRoutingDataSource,通过扩展这个类实现根据不同的请求切换数据源。

    AbstractRoutingDataSource继承AbstractDataSource,如果声明一个类DynamicDataSource继承AbstractRoutingDataSource后,DynamicDataSource本身就相当于一种数据源

    AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。
     

    需要实现determineCurrentLookupKey方法。

    1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    2. protected abstract Object determineCurrentLookupKey();
    3. }

    2. 实现逻辑

    1.先创建一个多线程 线程数据隔离的类来存放DataSource。DynamicDataSourceContextHolder

    2.determineCurrentLookupKey()方法中通过这个类获取当前线程的DataSource

    3.将数据源交给Spring管理。DataSourceConfig

    4.AbstractRoutingDataSource类中,DataSource是通过Key-value的方式保存的,我们可以通过ThreadLocal来保存Key,从而实现数据源的动态切换。DynamicDataSource

    5.写一个AOP动态的切换数据源。DataSourceAspect

    二、源代码

    1. 修改配置文件类

    1. server:
    2. port: 8085
    3. spring:
    4. profiles: test
    5. datasource:
    6. local:
    7. username: root
    8. password: root
    9. driver-class-name: com.mysql.jdbc.Driver
    10. jdbc-url: jdbc:mysql://localhost:3306/hm49?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
    11. remote:
    12. username: root
    13. password: root
    14. driver-class-name: com.mysql.jdbc.Driver
    15. jdbc-url: jdbc:mysql://localhost:3306/hm49?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8

    2. 创建数据源枚举

    1. public enum DataSourceType {
    2. REMOTE,
    3. LOCAL
    4. }

    3. 数据源切换处理

    创建一个数据源切换处理类,有对数据源变量的获取、设置和情况的方法,其中threadlocal用于保存某个线程共享变量。

    1. public class DynamicDataSourceContextHolder {
    2. /**
    3. * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
    4. * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    5. */
    6. private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
    7. /**
    8. * 设置数据源变量
    9. * @param dataSourceType
    10. */
    11. public static void setDataSourceType(String dataSourceType){
    12. System.out.printf("切换到{%s}数据源", dataSourceType);
    13. CONTEXT_HOLDER.set(dataSourceType);
    14. }
    15. /**
    16. * 获取数据源变量
    17. * @return
    18. */
    19. public static String getDataSourceType(){
    20. return CONTEXT_HOLDER.get();
    21. }
    22. /**
    23. * 清空数据源变量
    24. */
    25. public static void clearDataSourceType(){
    26. CONTEXT_HOLDER.remove();
    27. }
    28. }

    4. 继承AbstractRoutingDataSource

    动态切换数据源主要依靠AbstractRoutingDataSource。创建一个AbstractRoutingDataSource的子类,重写determineCurrentLookupKey方法,用于决定使用哪一个数据源。

    这里主要用到AbstractRoutingDataSource的两个属性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默认目标数据源,targetDataSources(map类型)存放用来切换的数据源。

    1. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    2. import javax.sql.DataSource;
    3. import java.util.Map;
    4. public class DynamicDataSource extends AbstractRoutingDataSource {
    5. public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
    6. super.setDefaultTargetDataSource(defaultTargetDataSource);
    7. super.setTargetDataSources(targetDataSources);
    8. // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
    9. super.afterPropertiesSet();
    10. }
    11. /**
    12. * 根据Key获取数据源的信息
    13. *
    14. * @return
    15. */
    16. @Override
    17. protected Object determineCurrentLookupKey() {
    18. return DynamicDataSourceContextHolder.getDataSourceType();
    19. }
    20. }

    5. 注入数据源

    1. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. import org.springframework.boot.jdbc.DataSourceBuilder;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.springframework.context.annotation.Primary;
    7. import javax.sql.DataSource;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. @Configuration
    11. public class DataSourceConfig {
    12. @Bean
    13. @ConfigurationProperties("spring.datasource.remote")
    14. public DataSource remoteDataSource() {
    15. return DataSourceBuilder.create().build();
    16. }
    17. @Bean
    18. @ConfigurationProperties("spring.datasource.local")
    19. public DataSource localDataSource() {
    20. return DataSourceBuilder.create().build();
    21. }
    22. @Bean(name = "dynamicDataSource")
    23. @Primary
    24. public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
    25. Map targetDataSources = new HashMap<>();
    26. targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
    27. targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
    28. return new DynamicDataSource(remoteDataSource, targetDataSources);
    29. }
    30. }

    6. 自定义多数据源切换注解

    设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上

    1. import java.lang.annotation.*;
    2. @Target(ElementType.METHOD)
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Documented
    5. public @interface DataSource {
    6. /**
    7. * 切换数据源名称
    8. */
    9. DataSourceType value() default DataSourceType.REMOTE;
    10. }

    7. AOP拦截类的实现

    通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息,CONTEXT_HOLDER.set(dataSourceType)这里的数据源信息从我们设置的注解上面获取信息,如果没有设置就是用默认的数据源的信息。

    1. import org.aspectj.lang.ProceedingJoinPoint;
    2. import org.aspectj.lang.annotation.Around;
    3. import org.aspectj.lang.annotation.Aspect;
    4. import org.aspectj.lang.annotation.Pointcut;
    5. import org.aspectj.lang.reflect.MethodSignature;
    6. import org.springframework.core.annotation.Order;
    7. import org.springframework.stereotype.Component;
    8. import java.lang.reflect.Method;
    9. @Aspect
    10. @Order(1)
    11. @Component
    12. public class DataSourceAspect {
    13. @Pointcut("@annotation(com.promsing.mult.DataSource)") //需要切AbstractRoutingDataSource的子类
    14. public void dsPointCut() {
    15. }
    16. @Around("dsPointCut()")
    17. public Object around(ProceedingJoinPoint point) throws Throwable {
    18. MethodSignature signature = (MethodSignature) point.getSignature();
    19. Method method = signature.getMethod();
    20. DataSource dataSource = method.getAnnotation(DataSource.class);
    21. if (dataSource != null) {
    22. DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
    23. }
    24. try {
    25. return point.proceed();
    26. } finally {
    27. // 销毁数据源 在执行方法之后
    28. DynamicDataSourceContextHolder.clearDataSourceType();
    29. }
    30. }
    31. }

    8. 使用切换数据源注解

    1. import org.springframework.beans.factory.annotation.Autowired;
    2. import org.springframework.jdbc.core.JdbcTemplate;
    3. import org.springframework.web.bind.annotation.GetMapping;
    4. import org.springframework.web.bind.annotation.RestController;
    5. import java.util.List;
    6. import java.util.Map;
    7. @RestController
    8. public class EmpController {
    9. @Autowired
    10. JdbcTemplate jdbcTemplate;
    11. @GetMapping("/local")
    12. @DataSource(value = DataSourceType.LOCAL)
    13. public List> local(){
    14. String dataSourceType = DynamicDataSourceContextHolder.getDataSourceType();
    15. System.out.println(dataSourceType);
    16. List> maps = jdbcTemplate.queryForList("select * from tb_user");
    17. return maps;
    18. }
    19. @GetMapping("/remote")
    20. @DataSource(value = DataSourceType.REMOTE)
    21. public List> remote(){
    22. String dataSourceType = DynamicDataSourceContextHolder.getDataSourceType();
    23. System.out.println(dataSourceType);
    24. List> maps = jdbcTemplate.queryForList("select * from tb_user");
    25. return maps;
    26. }
    27. }

    9. 在启动项目的过程中会发生循环依赖的问题,直接修改启动类即可

    1. import org.springframework.boot.SpringApplication;
    2. import org.springframework.boot.autoconfigure.SpringBootApplication;
    3. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    4. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    5. public class SpringbootDataApplication {
    6. public static void main(String[] args) {
    7. SpringApplication.run(SpringbootDataApplication.class, args);
    8. }
    9. }

    10. 启动成功

    请求:http://localhost:8085/local

    打印日志:切换到{LOCAL}数据源LOCAL

    请求:http://localhost:8085/remote

    打印日志:切换到{REMOTE}数据源REMOTE

     

  • 相关阅读:
    在普通频道输入字符串如果是pi,则给出信息~!
    64ELK日志分析系统
    spark运行报错
    [论文阅读]Visual Attention Network原文翻译
    复制文件描述符(dup、dup2函数) 和 文件共享
    MySQL视图详解
    五、Express
    迎接金九银十的狂风暴雨,最强Java面经八股文,跳槽必备。
    面试突击58:truncate、delete和drop的6大区别
    Git常用命令
  • 原文地址:https://blog.csdn.net/promsing/article/details/126446449