首页 > 其他分享 >自定义springboot-starter 动态数据源

自定义springboot-starter 动态数据源

时间:2023-11-26 15:36:50浏览次数:38  
标签:return 自定义 数据源 public master new class starter

自定义springboot-starter 动态数据源

如果使用的是spring或springboot框架,spring提供了一个实现动态数据源的一个抽象类AbstractRoutingDataSource

当我们实现这个类后需要实现一个方法

@Override
protected Object determineCurrentLookupKey() {

}

spring获取连接代码最终会走到AbstractRoutingDataSource类中的determineTargetDataSource方法

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    //主要这一段
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
}

通过this.resolvedDataSources.get(lookupKey);来获取一个dataSource之后才能获取连接

resolvedDataSources是AbstractRoutingDataSource类中的一个map类型的变量,里面的数据是在afterPropertiesSet方法时从targetDataSources获取的

@Override
public void afterPropertiesSet() {
  ....
    this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
  ....
}

也就是说通过设置map的k-v,再通过determineCurrentLookupKey方法返回对应的key,就可以进行数据源的切换

首先创建一个配置类用来保存每个数据源的信息

@Component(value = "dynamicDataSourceConfig")
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceConfig{

    private Map<String, DataSourceProperties> dataSources;

    public Map<String, DataSourceProperties> getDataSources() {
        return dataSources;
    }

    public void setDataSources(Map<String, DataSourceProperties> dataSources) {
        this.dataSources = dataSources;
    }

}

之后创建一个ThreadLocal持有类来保存每个线程需要的数据源

public class DynamicContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void choose(String dbName) {
        CONTEXT_HOLDER.set(dbName);
    }

    public static String get() {
        return CONTEXT_HOLDER.get();
    }

    public static void clear() {
        CONTEXT_HOLDER.remove();
    }
}

最后创建一个继承AbstractRoutingDataSource的子类,功能就完成了

@Component
public class RoutingDataSource extends AbstractRoutingDataSource {


    public RoutingDataSource(DynamicDataSourceConfig dynamicDataSourceConfig) {
        HashMap<Object, Object> routingDataSourceMap = parseConfig(dynamicDataSourceConfig);
        this.setTargetDataSources(routingDataSourceMap);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicContextHolder.get();
    }

    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }

    private HashMap<Object, Object> parseConfig(DynamicDataSourceConfig config) {
        Map<String, DataSourceProperties> map = config.getDataSources();
    
        HashMap<Object, Object> routingDataSourceMap = new HashMap<>();
        for (Map.Entry<String, DataSourceProperties> entry : map.entrySet()) {
            String dataSourceKey = entry.getKey();
            DataSourceProperties sourceProperties = entry.getValue();

            String driverClassName = sourceProperties.getDriverClassName();
            String url = sourceProperties.getUrl();
            String username = sourceProperties.getUsername();
            String password = sourceProperties.getPassword();

            Class<Driver> driverClass;
            try {
                driverClass = (Class<Driver>) Class.forName(driverClassName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            try {
                SimpleDriverDataSource source = new SimpleDriverDataSource(
                        driverClass.newInstance(),
                        url,
                        username,
                        password);
                routingDataSourceMap.put(dataSourceKey, source);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return routingDataSourceMap;
    }
}

首先是将数据库连接配置删除掉,修改为如下代码

dynamic:
  default-data-source-key: master
  data-sources:
    master:
      url: jdbc:mysql://127.0.0.1/master_data_source?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      url: jdbc:mysql://127.0.0.1/slave_data_source?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

测试代码

@GetMapping("master")
public List<User> master() {
    DynamicContextHolder.choose("master");
    System.out.println(userService.list());
    return null;
}

@GetMapping("slave")
public List<User> slave() {
    DynamicContextHolder.choose("slave");
    System.out.println(userService.list());
    return null;
}

结果

[User{id=1, name=master_user_1}, User{id=2, name=master_user_2}]
[User{id=1, name=slave_user_1}, User{id=2, name=slave_user_2}]

功能就算完成了,只需要在方法调用前指定使用的数据源即可,也可以给加个aop,更方便点

然后我就想给他整一个starter,starter相比代码中直接写会有两个问题,一个是spring如何将外部的类添加到容器中,另一个是容器扫描的顺序

第一个:在starter项目中resources文件夹下添加META-INF/spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
work.jame.dynamic.config.DynamicDataSourceConfig

这个文件会被springboot扫描,之后去加载里面指定的类,我们有一个类能被springboot加载到后其他的也都不是问题了

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component(value = "dynamicDataSourceConfig")
//这个注解必须要加,否则先去加载spring默认的数据源了
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
//加载其他类的类
@Import(AutoHandler.class)
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceConfig {

    private Map<String, DataSourceProperties> dataSources;

    private String defaultDataSourceKey = "master";


}
@Configuration
public class AutoHandler {

    @Autowired
    private DynamicDataSourceConfig config;

    @Bean
    public DataSource abstractRoutingDataSource() {
        return new RoutingDataSource(config);
    }

    //使用aop扫描方法注解,方便使用
    @Bean
    public Advisor dynamicDataSourceAnnotationAdvisor() {
        DynamicDataSourceAnnotationInterceptor interceptor = new 
            DynamicDataSourceAnnotationInterceptor(config.getDefaultDataSourceKey());
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
        advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return advisor;
    }
}

代码都在git上了,功能不难

https://gitee.com/sunankang/dynamic-data-source/tree/master

下面说说写这个过程中遇到的问题和解决办法吧

第一个就是我把代码挪到starter后,启动项目启动一直报

我一看,经典的导入了连数据库的配置,但是没配spring-datasource下url的错误,但是我就是不要配url,账号密码,用自定义的动态配置,我没有把代码抽出去之前还是可以用的,抽出去之后就不行了,很纳闷,看容器中也有自定义的动态数据源的类,后来我一想,之前见到过一个动态数据源的开源框架,和mybatis-plus是一个组织的,然后就去下载了,抄袭借鉴下别人是怎么做的

打开之后根本不知道从哪开始看,类太多了,模块也很多,然后我git切换到了一个比较早的版本,类还比较少

这个注解意思是在指定的类之前进行注入,然后就去看了下这个DataSourceAutoCoonfiguration类

这个注解是当容器中没有DataSource类型的bean时,才执行下面的这个方法,这个方法上的@Import导入了DataSourceConfiguration.Hikari.class,springboot默认的数据池就是Hikari,到这里其实已经大概明白了,当前项目加载bean和外部扫出的bean执行顺序应该是不一样的,所以在项目中直接写没问题,抽出个starter就报错了

标签:return,自定义,数据源,public,master,new,class,starter
From: https://www.cnblogs.com/sunankang/p/17857299.html

相关文章

  • C语言自定义数据类型-结构体
    在讨论自定义数据类型之前,我们不妨先回忆一下C语言的内置类型。例如字符型的char,整型中的intshortlong以及浮点型的floatdouble,这些都会C语言本身提供的数据类型,但仅仅有这些,是不足以满足我们的开发的。那么也就意味着需要一些复杂类型来帮助我们实现对复杂对象的操作,例如结构......
  • Hive学习路线-自定义函数
    九、自定义函数1.查看系统提供的函数列表showfunctions;2.查看具体某一个函数的描述信息descfunction[extended]函数名称;3.自定义函数Userdefinedfunction/UDF3.1创建一个java项目,导入hive的libs3.2创建类,继承org.apache.hadoop.hive......
  • el-table 字段自定义排序
    我在element-ui中使用el-table排序,默认开启就是el-table-column上加个sortable即可,但是后端返回的数据中含有中文列如tableData中有个字段count对应值是类似 13,6,2,3,4,5,10以上,7,含有中文‘以上’两个字,这个时候自带的排序已经无法满足我的要求,所以需要增加该列的自定义排......
  • 爱芯元智AX650N部署yolov8s 自定义模型
    爱芯元智AX650N部署yolov8s自定义模型本博客将向你展示零基础一步步的部署好自己的yolov8s模型(博主展示的是自己训练的手写数字识别模型),本博客教你从训练模型到转化成利于Pulsar2工具量化部署到开发板上训练自己的YOLOv8s模型准备自定义数据集数据集结构可以不像下面一样,......
  • C语言【自定义数据类型、typedef、动态内存分配】
    C语言【自定义数据类型、typedef、动态内存分配】一、自定义数据类型。​ 关于下面讲到的所有自定义数据类型(enum、struct、union),有一点要说的是:定义类型不是声明变量,做这步操作时不分配内存,也不能在定义类型时赋值(枚举那个不是赋值,是做一个限定,赋值时赋限定之外的值也不报错。)......
  • 使用druid自定义拦截器
         使用druid自定义的拦截器StatFilter,是可以通过日志进行慢sql打印的。但是如果想要把慢sql放入DB,或者通过钉钉告警的方式进行实时打印,则需要实现自定义的拦截器。     第一步:重新自定义拦截器    packagecom.example.demo.filter;importcom.a......
  • CQ 社区版 V2.6.0 发布 | SQL闪回、权限看板、新增数据源人大金仓等
    HELLO,大家好,又到了CloudQuery社区版发版时间!本次更新版本为v2.6.0,亮点多多,我们直入主题一起来看!本期亮点新增3种数据源支持V2.6.0,新增三种国产数据源支持:人大金仓(forOracle/PG)(8.6.0)、Gbase(3.3.0.2)、神州通用(7.0.8),对这些数据源支持权限管控、数据保护、审计分析等。目前V2.6......
  • Service 服务详解 及自定义服务模板
    文章目录1、服务简介2、服务的生命周期1)Service的启动停止2)、服务的生命周期的方法3、使用startService启动后服务的生命周期1)、文件结构2)activity_main.xml文件3)、myService自定义服务文件4)、MainActivity文件5)、AndroidManifest.xml文件6)、打印的相关log5、使用bindS......
  • uniapp+vue3中使用swiper和自定义header实现左右滑动的Tabs功能
    首先创建一个Tabs的Header,包含有一个下划线的指示器,在点击tabs的标题时候下划线会跟着动态的滑动下面是完整的Tabs的代码,可以看到定义了Tabs的background颜色样式,包含tab的宽度indicatorWidth,以及下划线的颜色indicatorColor主要的是tabList属性,通过tabList传入对应的tab数组得......
  • Ossclient无法自动装配和包aliyun-oss-spring-boot-starter导入错误
    无法导包 aliyun-oss-spring-boot-starter 解决办法:把 aliyun-oss-spring-boot-starter换成即可<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.8.3......