• 分库分表番外:多数据源/动态数据源实现


    多数据源/动态数据源实现

    比如在电商网站中可能会有这样的需求:根据用户所在城市不同,查询不同城市的商品数据。而在后台,这些不同城市的数据被分配在不同的数据库当中。很多人想当然的就觉得需要使用ShardingJDBC来实现多数据源管理。
    这种场景就是一个典型的多数据源切换的场景。但是我们仔细分析这样的场景,他跟分库分表其实并不太相同。ShardingJDBC固然可以使用Hint策略实现快速的数据库分库查询。例如前端传进来一个cityId字段,然后在后台查询数据时,将cityId设置到HintManager中,通过定制Hint策略,将后续的SQL操作分配到目标数据源当中。Hint分片策略案例参考分库分表二:ShardingJDBC进阶实战案例上
    但是,其实这种场景跟分库分表还是有差距的,那么如果不使用ShardingJDBC如何实现多数据源的切换。

    方案一:JDBC自带的多数据源方案

    在springboot中整合好mybatis和web功能 ,能够实现普通的增删改查。
    yml配置

    spring:
      datasource:
        datasource1:
          url: jdbc:mysql://localhost/sharding_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        datasource2:
          url: jdbc:mysql://xxx/xmkf_zt?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
          username: xxx
          password: xxx
          driver-class-name: com.mysql.cj.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    配置文件中配置了两个数据源。在两个的数据源中创建表

    CREATE TABLE `sharding_user` (
      `id` bigint(11) NOT NULL AUTO_INCREMENT,
      `user_name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      `source` varchar(255) DEFAULT 'test',
      `create_time` datetime DEFAULT NULL,
      `update_time` datetime DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实体类 service 那些就忽略了 和普通的mybatis调用是一样的

    配置所有数据源DataSource

    @Configuration
    public class DataSourceConfig {
    
    	@Bean(name = "dataSource1")
    	@ConfigurationProperties(prefix = "spring.datasource.datasource1")
    	public DataSource dataSource1(){
    		// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
    		return DruidDataSourceBuilder.create().build();
    	}
    
    	@Bean(name = "dataSource2")
    	@ConfigurationProperties(prefix = "spring.datasource.datasource2")
    	public DataSource dataSource2(){
    		// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
    		return DruidDataSourceBuilder.create().build();
    	}
    	//事务管理器
    	@Bean
    	public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource){
    		DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
    		dataSourceTransactionManager.setDataSource(dataSource);
    		return dataSourceTransactionManager;
    	}
    
    }
    
    • 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

    配置动态数据源

    @Component("dynamicDataSource")
    @Primary
    public class DynamicDataSource extends AbstractRoutingDataSource {
    	//当前线程
    	public static ThreadLocal<String> dataSourceName = new ThreadLocal<>();
    	@Resource
    	private DataSource dataSource1;
    	@Resource
    	private DataSource dataSource2;
    
    	@Override
    	protected Object determineCurrentLookupKey() {
    		//从当前线程中获取数据源
    		return dataSourceName.get();
    	}
    
    	@Override
    	public void afterPropertiesSet() {
    		Map<Object, Object> targetDataSources = new HashMap<>();
    		//假设B 代表北京的数据源
    		targetDataSources.put("B",dataSource1);
    		//假设X 代表厦门的数据源
    		targetDataSources.put("X",dataSource2);
    		//初始化 设置所有数据源
    		super.setTargetDataSources(targetDataSources);
    		//设置 默认数据源
    		super.setDefaultTargetDataSource(dataSource2);
    		super.afterPropertiesSet();
    	}
    }
    
    • 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

    测试代码:

    	@GetMapping("/testWriteDynamic")
    	@ApiOperation(value = "测试动态数据源插入")
    	@ApiOperationSupport(order = 9, author = "lsx")
    	public R testWriteDynamic(@RequestParam(value = "dsKey",defaultValue = "B") String dsKey){
    		List<ShardingUser> list = new ArrayList<>();
    		for (int i = 0; i < 20; i++) {
    			ShardingUser user = new ShardingUser();
    			user.setAge(i);
    			user.setUserName("lsx");
    			user.setCreateTime(new Date());
    			list.add(user);
    		}
    		//从前端获取数据源 默认为北京的数据源
    		DynamicDataSource.dataSourceName.set(dsKey);
    		shardingUserService.saveBatch(list);
    		return R.success("成功");
    	}
    
    	@GetMapping("/testReadDynamic")
    	@ApiOperation(value = "测试动态数据源读取")
    	@ApiOperationSupport(order = 10, author = "lsx")
    	public R testReadDynamic(@RequestParam(value = "dsKey",defaultValue = "X") String dsKey){
    		//从前端获取数据源 默认为厦门的数据源
    		DynamicDataSource.dataSourceName.set(dsKey);
    		List<ShardingUser> list = shardingUserService.list();
    		return R.data(list);
    	}
    
    • 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

    测试插入北京数据源
    在这里插入图片描述
    测试查询厦门数据源
    在这里插入图片描述
    由于厦门数据源没有插入数据所以为空

    测试查询北京数据源
    在这里插入图片描述
    查询成功

    测试插入数据到厦门数据源
    在这里插入图片描述
    然后查询厦门数据源
    在这里插入图片描述
    成功
    这种方案的好处是根据前端传入的参数决定使用哪个数据源。

    方案二:使用动态数据源框架

    引入依赖

    		<dependency>
                <groupId>com.baomidougroupId>
                <artifactId>dynamic-datasource-spring-boot-starterartifactId>
                <version>3.5.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    yml配置

    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        #使用dynamicDatasource框架
        dynamic:
          #设置默认的数据源或者数据源组
          primary: XM
          #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
          strict: false
          datasource:
            XM:
              url: jdbc:mysql://localhost/sharding_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
              username: root
              password: root
              driver-class-name: com.mysql.cj.jdbc.Driver
            BJ:
              url: jdbc:mysql://xxx/xmkf_zt?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
              username: xxx
              password: xxx
              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

    引入了这个框架,配置好数据源就不需要在有额外的配置了。这个框架自动帮我们配置好动态数据源
    用法
    在service 实现类添加@DS()注解 通过DS注解指定数据源 方法上的DS注解会覆盖类上的DS注解

    @Service
    @DS("bj")
    public class ShardingUserSerivceImpl extends ServiceImpl<ShardingUserMapper, ShardingUser> implements IShardingUserService{
    	@DS("xm")
    	@Override
    	public boolean saveEntity(ShardingUser entity) {
    		return this.save(entity);
    	}
    	@DS("xm")
    	@Override
    	public List<ShardingUser> queryList() {
    		return this.list();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    测试代码

    @GetMapping("/testWriteDynamic")
    	@ApiOperation(value = "测试动态数据源插入")
    	@ApiOperationSupport(order = 9, author = "lsx")
    	public R testWriteDynamic(@RequestParam(value = "dsKey",defaultValue = "B") String dsKey){
    		for (int i = 0; i < 20; i++) {
    			ShardingUser user = new ShardingUser();
    			user.setAge(i);
    			user.setUserName("lsx");
    			user.setCreateTime(new Date());
    			shardingUserService.saveEntity(user);
    		}
    		return R.success("成功");
    	}
    
    	@GetMapping("/testReadDynamic")
    	@ApiOperation(value = "测试动态数据源读取")
    	@ApiOperationSupport(order = 9, author = "lsx")
    	public R testReadDynamic(@RequestParam(value = "dsKey",defaultValue = "X") String dsKey){
    		List<ShardingUser> list = shardingUserService.list();
    		return R.data(list);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上述插入和查询都会去xm的数据源操作

  • 相关阅读:
    物联网主机E6000引领工业自动化的新篇章
    ACM-BCB2019 | SMILES-BERT:基于大规模无监督预训练的分子属性预测模型
    【广度优先搜索】leetcode 111. 二叉树的最小深度
    黑马点评(十二) -- UV统计
    远程服务器配置 Anaconda 并安装 PyTorch 详细教程
    springboot2自动加载sql文件
    C语言函数指针简介
    Resin反序列化链分析
    git切换分支
    GO文件相关操作使用
  • 原文地址:https://blog.csdn.net/admin522043032/article/details/126869562