动态数据源切换方案(一)

项目环境jeecg-boot + activity + shiro

需求描述:

根据登录时使用的机构,选择机构对应的数据库,实现机构业务数据的隔离,各机构数据库的表结构完全一致。

当前方案问题描述:

(1)客户端使用mysql的驱动访问mycat

    使用数据库中间件mycat 作为数据库访问的中间件,mycat 会分发数据库访问请求到后端数据库。因为mycat 

会把其他数据库模拟成MysqlServer来使用,项目中后端数据库使用国产数据库达梦,在Activity 工作流引擎启动时,

会先检测Activity 使用到的核心表是否存在,如果不存在则自动创建。这时项目中的数据源是mycat,当Activity

与mycat交互时,Activity会把mycat当作一个Mysql服务,因此Activity会以查询mysql元数据表的方式检查核心

表是否存在,当Activity发出以mysql的方式(innodb)在达梦数据库中创建核心表时出错了。

(2)客户端使用达梦的驱动访问mycat

 达梦的驱动包访问mycat 会出现通信异常

解决方案思路:

去掉数据库中间件,使用动态数据源的方式,来切换数据库访问。

方案一:(在登录时指定 routing-key )

Spring 内置了一个 AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的

数据源。因为AbstractRoutingDatasource 也是一个DataSource接口,因此,应用程序可以先设置好key,访问数据库的代码就可以

从AbstractRoutingDatasource 拿到对应的一个真实的数据源,从而访问指定的数据库。

       

测试环境准备:

(1)创建三个数据库:jeecg、db1、db2(这三个库都用jeecg-boot 的建库语句创建,表结构完全一致)

(2)创建测试用表:

CREATE TABLE `test` (
  `NAME` varchar(32) DEFAULT NULL COMMENT '数据库名称'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

注意:分别在三个库分别创建这张表,并添加对应的值

(3)在用户表中创建三个用户,分别对应三个库,使用post字段记录用户对应的数据库

insert  into `sys_user`(`id`,`username`,`realname`,`password`,`salt`,`avatar`,`birthday`,`sex`,`email`,`phone`,`org_code`,`status`,`del_flag`,`third_id`,`third_type`,`activiti_sync`,`work_no`,`post`,`telephone`,`create_by`,`create_time`,`update_by`,`update_time`,`user_identity`,`depart_ids`,`rel_tenant_ids`,`client_id`)
values
('1440505914032746498','db1','db1','7e6aba936f80ec0e67548952f27077a1','wXUzx9cx',NULL,'2021-09-22 10:38:33',1,'767608432@qq.com','13718444519','A01',1,0,NULL,NULL,1,'001','db1',NULL,NULL,'2021-09-22 10:39:08',NULL,NULL,2,'c6d7cb4deeac411cb3384b1b31278596','1','{\"beanName\":\"db1\",\"driverClassName\":\"com.mysql.cj.jdbc.Driver\",\"password\":\"root\",\"testOnBorrow\":true,\"url\":\"jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai\",\"username\":\"root\",\"validationQuery\":\"select 1\"}')
,('1440556389574676482','db2','db2','453fecdd611ddf8a','fmW6aCnl',NULL,'2021-09-22 13:59:07',1,'767608438@qq.com','13718444516','A01',1,0,NULL,NULL,1,'002','db2',NULL,NULL,'2021-09-22 13:59:42',NULL,NULL,1,'',NULL,'{\"beanName\":\"db2\",\"driverClassName\":\"com.mysql.cj.jdbc.Driver\",\"password\":\"root\",\"testOnBorrow\":true,\"url\":\"jdbc:mysql://127.0.0.1:3306/db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai\",\"username\":\"root\",\"validationQuery\":\"select 1\"}'),('3d464b4ea0d2491aab8a7bde74c57e95','zhangsan','张三','02ea098224c7d0d2077c14b9a3a1ed16','x5xRdeKB','https://static.jeecg.com/temp/jmlogo_1606575041993.png',NULL,NULL,NULL,NULL,'财务部',1,0,NULL,NULL,1,'0005','总经理',NULL,'admin','2020-05-14 21:26:24','admin','2020-09-09 14:42:51',1,'','',NULL),('a75d45a015c44384a04449ee80dc3503','jeecg','jeecg','58a714412072f0b9','mIgiYJow','https://static.jeecg.com/temp/国炬软件logo_1606575029126.png',NULL,1,NULL,NULL,'A02A01',1,0,NULL,NULL,1,'00002','devleader',NULL,'admin','2019-02-13 16:02:36','admin','2020-11-26 15:16:05',1,'',NULL,NULL)
,('e9ca23d68d884d4ebb19d07889727dae','admin','管理员','cb362cfeefbf3d8d','RCGTeGiH','https://static.jeecg.com/temp/国炬软件logo_1606575029126.png','2018-12-05 00:00:00',1,'jeecg@163.com','18611111111','A01',1,0,NULL,NULL,1,'00001','master',NULL,NULL,'2019-06-21 17:54:10','admin','2020-07-10 15:27:10',2,'c6d7cb4deeac411cb3384b1b31278596','',NULL);

实现步骤:

第一步:配置多数据源

datasource:
  master:
    url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 多数据源配置
  db1:
    url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  db2:
    url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

第二步:编写RoutingDataSource

使用Spring 内置的RoutingDataSource,把真实的数据源代理为一个动态数据源

package org.jeecg.config.init;


import org.apache.shiro.SecurityUtils;
import org.jeecg.common.system.vo.LoginUser;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;


public class DynamicDataSource extends AbstractRoutingDataSource {


    public  DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
    super.setDefaultTargetDataSource(defaultTargetDataSource);
    super.setTargetDataSources(targetDataSources);
    super.afterPropertiesSet();
  }

  @Override
  protected Object determineCurrentLookupKey() {

    //取登录信息
    try {
      LoginUser loginUser=(LoginUser)SecurityUtils.getSubject().getPrincipal();
      if (null == loginUser) {
        //如果未登录,返回null,让应用获取到主数据源进行登录
        return null;
      }
      //用post 字段记录登录用户的机构信息,这里为简化操作,机构和数据源的key保持一致
      String sysOrgCode = loginUser.getPost();
      logger.debug("current login user related database is " + sysOrgCode );
      if (sysOrgCode == null) {
        return null;
      }

      return sysOrgCode;
    } catch (Throwable se) {
      return null;
    }
  }
}

 第三步: 初始化动态数据源,并把SqlSessionFactory与动态数据源绑定

package org.jeecg.config.datasource;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.jeecg.config.init.DynamicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class MultiDataSourceConfig {

    //实体类位置
    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;

    //实体类位置
    @Value("${mybatis-plus.type-aliases-package}")
    private String typeAliasesPackage;

    @Bean(name = "master")
    @ConfigurationProperties("spring.datasource.dynamic.datasource.master")
    public DataSource masterDataSource() {
        return  DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "db1")
    @ConfigurationProperties("spring.datasource.dynamic.datasource.db1")
    public DataSource db1DataSource() {
        return  DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "db2")
    @ConfigurationProperties("spring.datasource.dynamic.datasource.db2")
    public DataSource db2DataSource() {
        return  DruidDataSourceBuilder.create().build();
    }

    /**
     * 多数据源动态切换
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(@Qualifier("master") DataSource masterDataSource,
                                        @Qualifier("db1") DataSource db1Datasource,
                                        @Qualifier("db2") DataSource db2Datasource) {

        //多数据源设置
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);

        //以下数据源应该从主数据库表里面读出来,目前是直接写死在代码里
        //初始化db1数据源
        targetDataSources.put("db1", db1Datasource);
        targetDataSources.put("db2", db2Datasource);
        return new DynamicDataSource(masterDataSource, targetDataSources);

    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(
            PaginationInterceptor paginationInterceptor,
            @Qualifier("dynamicDataSource") DataSource multiDataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multiDataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        //添加分页功能
        sqlSessionFactory.setPlugins(new Interceptor[]{
                paginationInterceptor           
        });
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        //配置mapper位置
        sqlSessionFactory.setMapperLocations(resolver.getResources(mapperLocations));  
        //配置实体类位置
        sqlSessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        return sqlSessionFactory.getObject();

    }
}

 使用admin 登录,查询test 表对应的数据源为master

 使用db1 登录,查询test 表对应的数据源为db1 

使用db2 登录,查询test 表对应的数据源为db2

 通过测试,当登录用户不同时,确实切换成了不同的数据源

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独行客-编码爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值