shardingsphere 4.1.1多主多从集成dynamic多数据源与mybatis-plus

shardingsphere 4.1.1多主多从集成dynamic多数据源与mybatis-plus

最近接到一个需求,在一个老旧的项目中,需要集成读写分离框架,并且后续需要考虑扩展到分库存储,基于以上考虑,我将技术框架锁定在了shardingsphere

一、简介

Shardingsphere的源码地址:https://github.com/apache/shardingsphere/tree/4.1.1

Shardingsphere官网地址:https://shardingsphere.apache.org/

Apache ShardingSphere 定位为 Database Plus,旨在构建异构数据库之上的标准层和生态系统。它侧重于如何重用现有的数据库及其各自的上层,而不是创建一个新的数据库。目标是最小化或消除底层数据库碎片化带来的挑战

二、业务背景

我们的项目使用到了dynamic-datasource-spring-boot-starter实现动态数据源切换功能,我们的这个项目需要按分类分别调用不同的数据库,并且每个数据库还新增了一个从库,后续可能会增加从库来提升读的效率

我们的实际业务一次请求只需要切换一次数据源,所以重写了源框架获取数据源的操作

三、实现业务需求代码

1、创建一个springboot项目,并且配置对应的maven配置

<!-- springboot 项目配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis-plus相关配置-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.0</version>
        </dependency>
        <!-- 自动生成代码工具-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>org.realityforge.org.jetbrains.annotations</groupId>
            <artifactId>org.jetbrains.annotations</artifactId>
            <version>1.7.0</version>
        </dependency>
        <!-- alibaba数据源配置-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!--shardingsphere版本配置 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <!--mysql连接配置 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <!--多数据源切换配置 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>

2、配置数据源与sharding

spring:
  port: 9090     #端口号
  application:
    name: shardingdemo  #app名称
  datasource:
    dynamic:
      strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
      datasource:
        master:
          type: com.alibaba.druid.pool.DruidDataSource        # 连接池
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://数据库IP:端口/数据库名(普通形式数据库)   #用于不做读写分离与分库分表的数据库           
          username: root                                      
          password: hiTKKihv5HU6v9                                    
  shardingsphere:
    datasource:
      names: miningpoolmaster,miningpoolslave,miningprofitmaster,miningprofitslave                                 # 数据源名字
      miningpoolmaster:
        type: com.alibaba.druid.pool.DruidDataSource        # 连接池
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://数据库IP:端口/数据库名(master1)               # 主库地址
        username: root                                      # 主库用户名
        password: hiTKKihv5HU6v9                                    # 主库密码
      miningpoolslave:
        type: com.alibaba.druid.pool.DruidDataSource        # 连接池
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://数据库IP:端口/数据库名(slave1)               # 从库地址
        username: root
        password: hiTKKihv5HU6v9
      miningprofitmaster:
        type: com.alibaba.druid.pool.DruidDataSource        # 连接池
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://数据库IP:端口/数据库名(master2)               
        username: root
        password: hiTKKihv5HU6v9
      miningprofitslave:
        type: com.alibaba.druid.pool.DruidDataSource        # 连接池
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://数据库IP:端口/数据库名(slave2)                # 从库地址
        username: root
        password: hiTKKihv5HU6v9
    sharding:
      #配置数据源的读写分离,但是数据库一定要做主从复制
      master-slave-rules:
        # 配置主从名称,可以任意取名字
        ms0:
          master-data-source-name: miningpoolmaster
          slave-data-source-names: miningpoolslave
          load-balance-algorithm-type: round_robin
        ms1:
          master-data-source-name: miningprofitmaster
          slave-data-source-names: miningprofitslave
          load-balance-algorithm-type: round_robin
      default-data-source-name: ms0
      tables:
        help_center_menu:
#          logic-lable: help_center_menu
          actual-data-nodes: ms0.help_center_menu
#          database-strategy:
#            hint:
#              algorithm-class-name: com.example.shardingdemo.config.HintShardingKeyAlgorithm
    props:
      sql:
        show: true

1、解析配置:

spring.datasource.dynamic.strict:若配置为true,需要注意下面的datasource里面的默认数据源必须为master,否则会抛异常

解决办法:

1、datasource:

​ master:

2、dynamic.primary: 指定默认数据源

2、sharding配置

  • 首先将多个数据源配置好:miningpoolmaster,miningpoolslave,miningprofitmaster,miningprofitslave
    • 建议取别名,好像用_连接的时候会报错
  • 配置读写分离配置
    • 注意:将一组读写配置取一个组名,用于后续切换数据源操作
    • 设置默认数据源,可以设置为上面的组名
    • 配置需要路由的库,由于我的业务逻辑,需要按不同的表路由到指定的数据源中,并且每个数据源中的库表结构不一样,所以需要采用表路由方式将数据库进行与表绑定(适用与表不多的情况)

3、动态数据源配置

通过翻看Dynamic源码可知,它的主要配置类:DynamicDataSourceAutoConfiguration,所以我们可以进行如下的扩展:

package com.example.shardingdemo.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 com.example.shardingdemo.utils.SpringUtil;
import org.apache.shardingsphere.core.yaml.config.masterslave.YamlMasterSlaveRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.MasterSlaveDataSource;
import org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource.metadata.MasterSlaveDatabaseMetaData;
import org.apache.shardingsphere.shardingjdbc.spring.boot.sharding.SpringBootShardingRuleConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.ApplicationContext;
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.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Map;

/**
 * 动态数据源配置:
 *
 * 使用{@link com.baomidou.dynamic.datasource.annotation.DS}注解,切换数据源
 *
 * <code>@DS(DataSourceConfiguration.SHARDING_DATA_SOURCE_NAME)</code>
 *
 * @author wanping
 * @date 2022/4/25 上午10:36
 */
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfiguration {
    /**
     * 分表数据源名称
     */
    public static final String SHARDING_DATA_SOURCE_NAME = "sharding";
    /**
     * 动态数据源配置项
     */
    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;
    /**
     * shardingjdbc有四种数据源,需要根据业务注入不同的数据源
     *
     * <p>1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
     * <p>2. 主从数据源: masterSlaveDataSource;
     * <p>3. 脱敏数据源:encryptDataSource;
     * <p>4. 影子数据源:shadowDataSource
     *
     * shardingjdbc默认就是shardingDataSource
     *  如果需要设置其他的可以使用
     * @Resource(value="") 设置
     */
    @Lazy
    @Resource
    DataSource shardingDataSource;
    /**
     * 将shardingDataSource放到了多数据源(dataSourceMap)中
     * 注意有个版本的bug,3.1.1版本 不会进入loadDataSources 方法,这样就一直造成数据源注册失败
     */
    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = dynamicDataSourceProperties.getDatasource();
        return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSource> 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;
    }
}

4、编写测试案例

1、dao层

通过生成工具生成dao与entity

HelpCenterMenu:
package com.example.shardingdemo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 帮助中心菜单表
 * </p>
 *
 * @author wanping
 * @since 2022-04-24
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class HelpCenterMenu implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 菜单主键
     */
     @TableId(value = "menu_id", type = IdType.AUTO)
    private Long menuId;

    /**
     * 菜单名称
     */
    private String menuNm;

    /**
     * 菜单图片链接
     */
    private String menuImgUrl;

    /**
     * 菜单父Id
     */
    private Long pId;

    /**
     * 排序
     */
    private Integer sequence;

    /**
     * 是否禁用
     */
    @TableField("is_enable")
    private Boolean enable;

    /**
     * 语言类型(中文:Chinese,英文:English)
     */
    private String languageType;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 修改时间
     */
    private Date updateTime;

    /**
     * 菜单级别
     */
    private Integer menuLevel;


    public static final String MENU_ID = "menu_id";

    public static final String MENU_NM = "menu_nm";

    public static final String MENU_IMG_URL = "menu_img_url";

    public static final String P_ID = "p_id";

    public static final String SEQUENCE = "sequence";

    public static final String IS_ENABLE = "is_enable";

    public static final String LANGUAGE_TYPE = "language_type";

    public static final String CREATE_TIME = "create_time";

    public static final String UPDATE_TIME = "update_time";

    public static final String MENU_LEVEL = "menu_level";

}
HelpCenterMenuMapper:
package com.example.shardingdemo.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.shardingdemo.entity.HelpCenterMenu;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 * 帮助中心菜单表 Mapper 接口
 * </p>
 *
 * @author wanping
 * @since 2022-04-24
 */
@Mapper
public interface HelpCenterMenuMapper extends BaseMapper<HelpCenterMenu> {

}
HelpCenterMenuService:
package com.example.shardingdemo.service;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.example.shardingdemo.dao.HelpCenterMenuMapper;
import com.example.shardingdemo.entity.HelpCenterMenu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@DS("sharding")
public class HelpCenterMenuService {

    @Autowired
    private HelpCenterMenuMapper helpCenterMenuMapper;

    public List<HelpCenterMenu> selectList(){
        return helpCenterMenuMapper.selectList(null);
    }
  
  	public int insert(HelpCenterMenu helpCenterMenu){
        return helpCenterMenuMapper.insert(helpCenterMenu);
    }
}

注意:由于咋们的动态数据源默认选的是master,所以这里需要指定sharding数据源

ShardingdemoApplicationTests:
  • 测试写入
@Test
    void insertMenus(){
        HelpCenterMenu helpCenterMenu = new HelpCenterMenu();
        helpCenterMenu.setMenuId(IdWorker.getId());
        helpCenterMenu.setMenuNm("测试00005");
        helpCenterMenu.setSequence(10);
        helpCenterMenu.setEnable(false);
        helpCenterMenu.setLanguageType("Chinese");
        helpCenterMenu.setCreateTime(new Date(System.currentTimeMillis()));
        helpCenterMenuService.insert(helpCenterMenu);
    }

写入主库结果如下图:

  • 测试读取

    @Test
        void contextLoads() {
            List<HelpCenterMenu> helpCenterMenus = helpCenterMenuService.selectList();
            helpCenterMenus.forEach(f->{
                System.out.println(f.getMenuNm());
            });
        }
    

    读取从库如下图:

目录结构:

posted @ 2022-04-26 13:57  小学程序员  阅读(5596)  评论(3编辑  收藏  举报