Shardingsphere与dynamic-datasource配合实现多数据源切换

    所有的技术架构都是会因为业务场景变动的,逐步发展使用的,你没法去设计一个永久不变的技术架构。因为公司业务数据量的增加,必须对原有的表进行拆分,
    之前记录了一下,Shardingsphere实现读写分离+分库分表,但还是不能满足一些需求,因为要归集以前的旧数据,对数据进行迁移、整理,所以就需要项目同时能访问原来的库以及新的分库分表。本项目是基于springcloud架构搭建的,最简单的方案是搭建两个服务模块,一个对原有数据归集处理,然后一个模块插入到新的分库分表中去,Shardingsphere实现读写分离+分库分表
模块就是负责新业务数据的插入,以及查询的,但是因为不想新增服务,考虑在这个服务内新增数据源的方式。

业务场景:
一个主数据源(原来的旧数据) + 一个分库分表的(master) + 一个分库分表的(slave)

设计思路就是,配置一个数据源,为主数据源,使用dynamic-datasource来管理数据源的切换,并且把Shardingsphere管理的数据源加入进去。也就是对分表的SQL使用sharding jdb数据源,对不涉及到分表的SQL,使用普通数据源。

官网dynamic-datasource 说明
  1. 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD
  2. 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
  3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
  4. 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
  5. 方法上的注解优先于类上注解。
  6. DS支持继承抽象类上的DS,暂不支持继承接口上的DS
    如果需要配置从库,直接在datasource后面继续加就行,默认会使用master数据源,需要使用其他数据源时在service类或方法上加上@DS("数据源名称")注解就行。
    项目结构

ok 上代码,先添加关键依赖:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.3
         
    
    com.fanqie
    sharding-dynamic-atasource
    0.0.1-SNAPSHOT
    sharding-dynamic-atasource
    test project for Sharding And Dynamic-DataSource
    
        Hoxton.SR8
        1.2.44
        
        8.0.16
        1.1.23
        4.1.1
        3.2.1
    
    
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            com.alibaba
            druid
            ${druid.version}
        

        
        
            org.projectlombok
            lombok
            1.16.18
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.1
        
        
        
            mysql
            mysql-connector-java
            ${mysql.version}
        

        
        
            com.baomidou
            dynamic-datasource-spring-boot-starter
            ${dynamic-datasource.version}
        
        
        
            org.apache.shardingsphere
            sharding-jdbc-spring-boot-starter
            ${sharding-sphere.version}
        
        
            org.apache.shardingsphere
            sharding-core-common
            ${sharding-sphere.version}
        


    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        

        
            
                
                src/main/java
                
                    **/*.xml
                
            
            
                
                src/main/resources
                
                    **/*.yml
                    **/*.xml
                
            
        
    



注意:
最好是按照以上版本去使用,因为我发现,3.1.1版本有个bug, 不会进入loadDataSources方法,这样就一直造成数据源注册失败,暂时没去追踪源码是什么原因造成的,换了版本就可以了。

yml配置类

server:
  port: 8900
spring:
  main:
    allow-bean-definition-overriding: true
  datasource:
    dynamic:
      strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
      datasource:
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource
          url: jdbc:mysql://192.168.200.36:3306/seal_ma?useUnicode=true&allowMultiQuerie=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
          username: root
          password: 123456
  #shardingjdbc默认接管了所有的数据源,如果我们有多个非分表的库时,则最多只能设置一个为默认数据库,其他的非分表数据库不能访问
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql.show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,record*
      names: record1,record2
      # 给master-record1每个数据源配置数据库连接信息
      record1:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.200.5:3306/seal_sign_record?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      # 配置record2-slave
      record2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.200.20:3306/seal_sign_record?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
      #       配置数据源的读写分离,但是数据库一定要做主从复制
      master-slave-rules:
        # 配置主从名称,可以任意取名字
        ms:
          # 配置主库master,负责数据的写入
          master-data-source-name: record1
          # 配置从库slave节点
          slave-data-source-names: record2
          # 配置slave节点的负载均衡均衡策略,采用轮询机制
          load-balance-algorithm-type: round_robin
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ms
      #       配置分表的规则
      tables:
        ps_seal_log:
          actual-data-nodes: ms.ps_seal_log_$->{2020..2021}${(1..12).collect{t ->t.toString().padLeft(2,'0')} }
          table-strategy:
            standard:
              shardingColumn: action_time
              preciseAlgorithmClassName: com.fanqie.sd.config.DatePreciseShardingAlgorithm


# 整合mybatis的配置XXXXX
mybatis-plus:
  mapper-locations: classpath*:**/dao/*.xml
  type-aliases-package: com.fanqie.sd.entity

Shardeing的分库分表策略类

package com.fanqie.sd.config;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;

/**
 * @author fanqie
 * @ClassName  shaerding  分库分表策略配置:按照年月 如ps_seal_log202108
 * @date 2021/7/28 下午4:40
 **/

public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm  {


    private static final Logger logger = LoggerFactory.getLogger(DatePreciseShardingAlgorithm.class);
    @Override
    public String doSharding(Collection availableTargetNames, PreciseShardingValue preciseShardingValue) {
        Date date = preciseShardingValue.getValue();
        logger.info("Sharding input:" + preciseShardingValue.getValue());
        String suffix = getSuffixByYearMonth(date);
        for (String tableName : availableTargetNames) {
            logger.info("suffix:" + suffix + ", 表明:{}" + tableName);
            if (tableName.endsWith(suffix)) {
                return tableName;
            }
        }
        throw new IllegalArgumentException("未找到匹配的数据表");

    }

    private static String getSuffixByYearMonth(Date date) {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setMinimumIntegerDigits(2);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.YEAR)  +"" +  nf.format((calendar.get(Calendar.MONTH) + 1));
    }
}

Dynamic-datasource的动态数据源配置类

package com.fanqie.sd.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;

/**
 * 动态数据源配置:
 *
 * 使用{@link com.baomidou.dynamic.datasource.annotation.DS}注解,切换数据源
 *
 * @DS(DataSourceConfiguration.SHARDING_DATA_SOURCE_NAME)
 *
 * @author fanqie
 * @date 2021/8/16 下午12:36
 */
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfiguration {
    /**
     * 分表数据源名称
     */
    public static final String SHARDING_DATA_SOURCE_NAME = "sharding";
    /**
     * 动态数据源配置项
     */
    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;

    /**
     * shardingjdbc有四种数据源,需要根据业务注入不同的数据源
     *
     * 

1. 未使用分片, 脱敏的名称(默认): shardingDataSource; *

2. 主从数据源: masterSlaveDataSource; *

3. 脱敏数据源:encryptDataSource; *

4. 影子数据源:shadowDataSource * * shardingjdbc默认就是shardingDataSource * 如果需要设置其他的可以使用 * @Resource(value="") 设置 */ @Lazy @Resource DataSource shardingDataSource; /** * 将shardingDataSource放到了多数据源(dataSourceMap)中 * 注意有个版本的bug,3.1.1版本 不会进入loadDataSources 方法,这样就一直造成数据源注册失败 */ @Bean public DynamicDataSourceProvider dynamicDataSourceProvider() { Map datasourceMap = dynamicDataSourceProperties.getDatasource(); return new AbstractDataSourceProvider() { @Override public Map loadDataSources() { Map dataSourceMap = createDataSourceMap(datasourceMap); // 将 shardingjdbc 管理的数据源也交给动态数据源管理 dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource); return dataSourceMap; } }; } /** * 将动态数据源设置为首选的 * 当spring存在多个数据源时, 自动注入的是首选的对象 * 设置为主要的数据源之后,就可以支持shardingjdbc原生的配置方式了 * * @return */ @Primary @Bean public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) { DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(); dataSource.setPrimary(dynamicDataSourceProperties.getPrimary()); dataSource.setStrict(dynamicDataSourceProperties.getStrict()); dataSource.setStrategy(dynamicDataSourceProperties.getStrategy()); dataSource.setProvider(dynamicDataSourceProvider); dataSource.setP6spy(dynamicDataSourceProperties.getP6spy()); dataSource.setSeata(dynamicDataSourceProperties.getSeata()); return dataSource; } } **说明:**

访问主数据源的Service实现类

因为在yml中配置为默认数据源,所以可以忽略@DS注解

package com.fanqie.sd.service.impl;

import com.fanqie.sd.dao.dynamic.PsSealMapper;
import com.fanqie.sd.entity.PsSeal;
import com.fanqie.sd.service.PsSealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author fanqie
 * @ClassName PsSealServiceImpl
 * @date 2021/8/18 下午3:10
 **/
@Service
public class PsSealServiceImpl implements PsSealService {

    @Autowired
    private PsSealMapper sealMapper;


    @Override
    public PsSeal getSealWithValidDept(Long sealId) {
        return sealMapper.getSealWithValidDept(sealId);
    }
}

访问SharedingService实现类

需要增加数据源的别名,@DS("sharding"),这个名字是在DynamicDataSourceProperties中设置的,你可以任意取,但是必须是你注册的。

package com.fanqie.sd.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.fanqie.sd.dao.shareding.SealLogMapper;
import com.fanqie.sd.entity.SealLog;
import com.fanqie.sd.service.SealLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author fanqie
 * @ClassName SealLogServiceImpl
 * @date 2021/8/18 下午3:11
 **/
@Service
@DS("sharding")
public class SealLogServiceImpl implements SealLogService {

    @Autowired
    SealLogMapper sealLogMapper;

    @Override
    public int insert(SealLog record) {
        return sealLogMapper.insert(record);
    }

    @Override
    public SealLog selectByPrimaryKey(long id) {
        return sealLogMapper.selectByPrimaryKey(id);
    }
}

测试类

package com.fanqie.sd;

import com.fanqie.sd.entity.PsSeal;
import com.fanqie.sd.entity.SealLog;
import com.fanqie.sd.service.PsSealService;
import com.fanqie.sd.service.SealLogService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

@SpringBootTest
class ShardingDynamicAtasourceApplicationTests {


    /**
     *
     * 从主数据源中读取数据,也就是原项目中的大表
     */
    @Autowired
    PsSealService psSealService;

    @Test
    void selectPsSealByEsId() {
        PsSeal seal= psSealService.getSealWithValidDept(2278L);
        System.out.println(seal.toString());
    }




    /**
     *
     * 从shareding 配置数据源中读写数据,并且shareding做了读写分离 + 分库分表
     */
    @Autowired
    SealLogService sealLogService;

    @Test
    void insert() throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = "2021-08-03 10:59:27";
        Date date = simpleDateFormat.parse(dateStr);


        SealLog psSealLog = new SealLog();
        psSealLog.setActionTime(date);
        psSealLog.setBusinessType(1);
        psSealLog.setSealId(9L);
        psSealLog.setEsId("11010100007955");
        psSealLog.setSealDeptId(2L);
        psSealLog.setSealName("1027测试");
        int flag =  sealLogService.insert(psSealLog);
        System.out.println("flag=" + flag);

        SealLog psSealLog2 = new SealLog();
        psSealLog2.setActionTime(new Date());
        psSealLog2.setBusinessType(3);
        psSealLog2.setSealId(10L);
        psSealLog2.setEsId("44030800000093");
        psSealLog2.setSealDeptId(10L);
        psSealLog2.setSealName("测试");
        int flag2 =  sealLogService.insert(psSealLog2);
        System.out.println("flag2=" + flag2);



        SealLog psSealLog3 = new SealLog();
        psSealLog3.setActionTime(new Date());
        psSealLog3.setBusinessType(2);
        psSealLog3.setSealId(11L);
        psSealLog3.setEsId("13030600000131");
        psSealLog3.setSealDeptId(11L);
        psSealLog3.setSealName("恒大");
        int flag3 =  sealLogService.insert(psSealLog3);
        System.out.println("flag3=" + flag3);


        String dateStr2 = "2021-09-03 10:59:27";
        Date date2 = simpleDateFormat.parse(dateStr2);

        SealLog psSealLog4 = new SealLog();
        psSealLog4.setActionTime(date2);
        psSealLog4.setBusinessType(2);
        psSealLog4.setSealId(12L);
        psSealLog4.setEsId("51030300002297");
        psSealLog4.setSealDeptId(12L);
        psSealLog4.setSealName("深圳");
        int flag4 =  sealLogService.insert(psSealLog4);

        System.out.println("flag4=" + flag4);
    }


    @Test
    void search() throws ParseException {
        SealLog sealLog =  sealLogService.selectByPrimaryKey(10L);
        System.out.println("flag5=" + sealLog);
    }
}

你可能感兴趣的:(Shardingsphere与dynamic-datasource配合实现多数据源切换)