• 【项目实战】多租户实现数据库动态切换


    背景

    最近公司项目中需要做多租户进行数据源切换的业务,目的是要达到租户与租户之间的数据要进行隔离,本次实现的实现是在一台服务器中,去进行多个数据库之间的切换,且不能相互影响。有不明白多租户的相关内容,可以查看资料:
    多租户相关内容

    多数据源准备工作

    参考资料:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

    整体思路

    在请求发出的时候就去进行拦截(判断)这个用户所在的数据源,然后再去切换到对应的数据源中。
    1、在我们的业务中,用户可以自己注册,然后就会生产一个数据库,那么在新用户进行登录的时候就要去实时的切换数据源。
    2、那么对于已有的数据源,在项目启动的时候就去把所有的数据源加载到内存中。

    在这里插入图片描述

    多数据源切换方式

    在数据源切换中,找到有两种切换方式一个自动,另外一个是手动切换。

    准备工作

    添加依赖:使用最新版本

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
      <version>${version}</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    自动切换(@DS方式)

    配置文件设置

    自动切换也就是在配置文件中把需要用到的数据源准备好,这样就可以在使用的时候在注解上进行使用并进行切换。

    spring:
      datasource:
        dynamic:
          primary: master #设置默认的数据源或者数据源组,默认值即为master
          strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
          datasource:
            master: # 数据源名称
              url: jdbc:mysql://116.204.118.226:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: guyanshuang.
              driver-class-name: com.mysql.cj.jdbc.Driver
    # 如下,如果你是确定的几个数据源,可以直接都在yaml配置写死即可
            slave_1:
              url: jdbc:mysql://116.204.118.226:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: 123456
              driver-class-name: com.mysql.cj.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    切换数据源代码

    @RestController
    @RequestMapping("/eee")
    public class DataSourceTest {
        @Autowired
        private JdbcTemplate jdbcTemplate;
        @Autowired
        private DataSource dataSource;
    
        private DataSourceCreator dataSourceCreator;
        @DS("master")
        @GetMapping("/selectAll")
        public List selectAll() {
            return  jdbcTemplate.queryForList("select * from test");
        }
    
        @GetMapping("/selectByCondition")
        @DS("slave_1")
        public List selectByCondition() {
            return  jdbcTemplate.queryForList("select * from test");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里有两个方法,都使用@DS注解,但是他们的参数是不同的,这里也就是去切换到对应的数据源了。
    @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。

    手动切换

    配置文件设置

    这里是使用了druid的配置

    # 数据源配置
    spring:
      datasource:
        druid:
          destroy-method: close
          stat-view-servlet:
            enabled: true
            login-username: admin
            login-password: 123456
        dynamic:
          primary: master
          # /p6spy: true
          lazy: true
          # 配置全局druid参数,请按需配置
          druid:
            initial-size: 5
            max-active: 20
            min-idle: 5
            max-wait: 60000
          datasource:
            master:
              username: root
              password: 123456
              url: jdbc:mysql://localhost:3306/auth?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              driver-class-name: com.mysql.cj.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    项目启动加载数据源:使用注解@PostConstruct

    @PostConstruct
        public void initDataSource(){
            //查询所有的资源
           List<TanentModel> tanentModels =tantentMapper.queryAllDataSource();
    
            for (TanentModel tantent:tanentModels) {
                DataSourceProperty dataSourceProperty = new DataSourceProperty();
                dataSourceProperty.setUrl(tantent.getTenantUrl());
                dataSourceProperty.setDriverClassName(tantent.getTenantDriverClassName());
                dataSourceProperty.setUsername(tantent.getTenantUserName());
                dataSourceProperty.setPassword(tantent.getTenantUserPassword());
                dataSourceProperty.setPoolName(tantent.getTenantCode());
                //创建数据源
                javax.sql.DataSource createDataSource = dataSourceCreator.createDataSource(dataSourceProperty);
                //数据源添加到资源池中
                ((DynamicRoutingDataSource)dataSource).addDataSource(dataSourceProperty.getPoolName(),createDataSource);
    
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    添加数据源

    在这里插入代码片 public void switchDataSource(String phone) {
            TanentModel tanentModel=null;//获取到的数据源
            try {
                if (!StringUtils.isEmpty(phone)){
                    tanentModel= tantentMapper.queryDataSource(phone);
    
                    DataSourceProperty dataSourceProperty = new DataSourceProperty();
                    dataSourceProperty.setUrl(tanentModel.getTenantUrl());
                    dataSourceProperty.setDriverClassName(tanentModel.getTenantDriverClassName());
                    dataSourceProperty.setUsername(tanentModel.getTenantUserName());
                    dataSourceProperty.setPassword(tanentModel.getTenantUserPassword());
                    dataSourceProperty.setPoolName(tanentModel.getTenantCode());
                    String poolName=tanentModel.getTenantCode();
                    DynamicDataSourceContextHolder.clear();
                    //切换到对应poolName的数据源
                    // 判断数据源是否存在,不存在则添加
                    javax.sql.DataSource createDataSource = dataSourceCreator.createDataSource(dataSourceProperty);
                    //根据手机号查到的资源名称,获取资源池中的资源
                    DataSource dataSourcePool = ((DynamicRoutingDataSource) this.dataSource).getDataSources().get(poolName);
                    //如果资源为空,说明资源池中没有改资源,那么就添加到资源池中
                    if (dataSourcePool==null){
                        //动态添加到资源池
                        ((DynamicRoutingDataSource)dataSource).addDataSource(dataSourceProperty.getPoolName(),createDataSource);
                    }
                    DynamicDataSourceContextHolder.push(poolName);
                    String peek = DynamicDataSourceContextHolder.peek();
                    System.out.println("当前线程数据源:"+peek);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    添加数据源核心代码:

      DynamicDataSourceContextHolder.push(poolName);
    
    • 1

    总结

    多数据源切换是现代应用开发中的重要功能之一。对于数据源切换,我们可以采取两种方式:@DS切换和手动切换。

    @DS切换是一种自动化方式,通过在代码中配置切换规则,系统可以根据预设条件自动切换数据源。这种方式能够减少人工操作的繁琐,提高效率和准确性。它适用于在特定场景下需要频繁切换数据源的情况,例如负载均衡、读写分离等。通过@DS切换,我们可以灵活地管理和调度多个数据源,实现资源的优化利用和负载均衡,从而提升系统的性能和可扩展性。

    另一方面,手动切换是一种更加灵活的方式。它允许开发人员根据实际情况进行即时切换,通过编码的方式直接指定使用哪个数据源。这种方式适用于需要根据具体需求灵活切换数据源的场景,例如特定业务逻辑需要使用不同的数据源等。手动切换虽然需要人工介入,但它提供了更大的灵活性和可控性,使开发人员可以根据需要自由切换数据源,满足不同的业务需求。

    综上所述,针对多数据源切换,我们可以根据具体业务需求和系统特点选择合适的切换方式。@DS切换适用于频繁切换数据源、需要自动化调度的场景,而手动切换则更加灵活,适用于需要根据实际情况即时切换数据源的情况。通过合理的规划和技术实施,我们可以充分发挥各种切换方式的优势,实现数据源的灵活管理和高效切换,为业务运行提供有力支持。

  • 相关阅读:
    爆了,阿里架构师手写MySQL数据库指南,带你轻松年入百万
    物业服务神秘顾客监测的难点及对策
    206、3分钟学会路由器的无线桥接,超实用
    Java的反射慢在哪?
    java计算机毕业设计汽车售后服务信息管理系统的设计与实现源程序+mysql+系统+lw文档+远程调试
    Java:Swift与Java |你应该知道的最有价值的区别
    写Python爬虫又被屏蔽了,你现在需要一个稳定的代理IP
    CSS详细基础(六)边框样式
    设计模式——单例模式
    【Qt】关于QLabel显示图片二三事
  • 原文地址:https://blog.csdn.net/weixin_45309155/article/details/134483021