​​​​​​​Spring多租户数据源管理 AbstractRoutingDataSource

 更新时间:2022年05月16日 10:28:33   作者:​ 小姐姐味道   ​  
本文技术了​​​​​​​Spring多租户数据源管理 AbstractRoutingDataSource,下文详细内容介绍,需要的小伙伴可以参考一下

前言:

很多情况,我们确实需要在一个服务中访问多个数据源。虽然它让整体设计变的不那么优雅,但真实的世界确实需要它。比如,你的业务为两个比较大的客户服务,但你希望他们能够共用一套代码。

也就是说,你的代码刚开始没有考虑设计多租户这种功能,但后面又有这种蛋疼的需求。但还好不是爆炸式的租户增长。

除了引入一些分库分表组件,Spring自身提供了AbstractRoutingDataSource的方式,让多数数据源的管理成为可能。其实分库分表组件使用上限制很多,你不得不首先梳理这座屎山,接下来还要忍受中间件对你的SQL的苛刻要求;反而是一些野路子,能够让代码的改动量尽量的减少。

心动不如行动。接下来,就让我们来看一下它的具体实现吧。

1.基本原理

多数据源能进行动态切换的核心就是spring底层提供了AbstractRoutingDataSource类进行数据源路由。AbstractRoutingDataSource实现了DataSource接口,所以我们可以将其直接注入到DataSource的属性上。

我们主要继承这个类,实现里面的方法determineCurrentLookupKey(),而此方法只需要返回一个数据库的名称即可。

比如,Controller通过拿到前端业务传递的数值,进行业务逻辑分发。它就可以手动设置当前请求的数据库标识,然后路由到正确的库表里面。

@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;

然后,我们将它解析称properties。

@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();
    }
}

最后一步,设置整个项目中默认的DataSource。注意,我们生成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简单的多数据源管理。但明显的,它还存在很多问题。

  • 需要产品设计选择模式,进行业务切换。
  • 前端可以采用放在localStroage的方式,保存属性,可使用拦截器方式将变量每次都传递。
  • 后端每次请求,都需要带上目标db,可以采用放在ThreadLocal里的方式。但ThreadLocal有线程透传的问题,如果任务里开启了子线程,则变量不能共享。
  • 由于表是动态选择的,所以JPA自动创建和update等模式,将不可用。不方便测试和单元测试,在测试接口的时候,也需要每次强制指定指向的库。
  • 由于是修改数据源的模式,每次增加库,都需要重新启动上线才可以。如果要做到动态性,数据源销毁是个问题。

总结

对于一个微服务来说,有很多默认的限制策略,比如,不同域之间的服务是不能共享一个数据库的。这些基本原则,把微服务整的清清爽爽,是一些基本的原则。

同理的,如果我们在设计开始,就给每一张表加上租户的字段ID,那么写代码的时候就顺畅的多。但是世界上没有这么多如果。

原则为何而存在?当然是为了让人去打破的。

编程只是工具,反正代码在自己手里,怎么玩,看需要,也看心情。条条大路通罗马,曲径通幽处,风光无限好。

到此这篇关于Spring多租户数据源管理 AbstractRoutingDataSource的文章就介绍到这了,更多相关AbstractRoutingDataSource内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中绝对值函数的介绍与其妙用

    Java中绝对值函数的介绍与其妙用

    这篇文章主要给大家介绍了Java中绝对值函数的介绍与其妙用,其中包括绝对值函数用来获取表达式的绝对值和绝对值函数实现降序+升序输出。文章末尾给出了实例介绍,有需要的朋友们可以参考学习,下面来一起看看吧。
    2017-01-01
  • Spring MVC 使用支付宝接口完成在线支付的示例代码

    Spring MVC 使用支付宝接口完成在线支付的示例代码

    本篇文章主要介绍了Spring MVC 使用支付宝接口完成在线支付的示例代码,具有一定的参考价值,有兴趣的可以了解一下
    2017-08-08
  • Java编程接口回调一般用法代码解析

    Java编程接口回调一般用法代码解析

    本文的主要内容是同过实际代码向大家展示Java编程中接口回调的一般用法,具有一定参考价值,需要的朋友可以了解下
    2017-09-09
  • 基于springcloud异步线程池、高并发请求feign的解决方案

    基于springcloud异步线程池、高并发请求feign的解决方案

    这篇文章主要介绍了基于springcloud异步线程池、高并发请求feign的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • SpringBoot通过@Value实现给静态变量注入值详解

    SpringBoot通过@Value实现给静态变量注入值详解

    这篇文章主要介绍了springboot如何通过@Value给静态变量注入值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • SpringBoot通过请求对象获取输入流无数据

    SpringBoot通过请求对象获取输入流无数据

    这篇文章主要介绍了使用SpringBoot通过请求对象获取输入流无数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java实现的自定义类加载器示例

    Java实现的自定义类加载器示例

    这篇文章主要介绍了Java实现的自定义类加载器,结合具体实例形式分析了java自定义类加载器的原理与具体实现技巧,需要的朋友可以参考下
    2019-07-07
  • Java线程并发工具类CountDownLatch原理及用法

    Java线程并发工具类CountDownLatch原理及用法

    这篇文章主要介绍了Java线程并发工具类CountDownLatch原理及用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • nacos配置在代码中引用的方法讲解

    nacos配置在代码中引用的方法讲解

    这篇文章主要介绍了nacos配置在代码中如何引用,如果主配置中配置的内容和拓展配置的内容重复则按主配置的配置 ,如果拓展配置中的内容和另一个拓展配置中的内容重复,则按下标大的配置作为最终的配置,对nacos配置代码引用相关知识感兴趣朋友一起看看吧
    2022-12-12
  • java基于JSON实现前后端交互(附代码)

    java基于JSON实现前后端交互(附代码)

    本文主要介绍了java基于JSON实现前后端交互,通过实际代码示例展示了如何前后端JSON交互,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01

最新评论