基于springboot + sharding jdbc + mybatisplus + druid,分库分表配置

写在前面

sharding jdbc 提供了多种接入方式。我这里只介绍两种接入方式:

  1. 引入sharding-jdbc的springboot starter包,通过application.yml进行配置(当前文章的主要介绍内容)
  2. 通过java代码的硬编码方式接入(比较推荐,下一篇文章详细介绍https://www.jianshu.com/p/3254ac3bc414

分库分表工具介绍:ShardingSphere

这里我就不过多赘述,直接贴上ShardingSphere的官网地址,想深入了解的小伙伴可以自行查阅
https://shardingsphere.apache.org/document/current/cn/overview/
ps:(小声逼逼一句)我觉得这个官方文档,只适合看看介绍,如果真的要实战操作,根据这个官方文档写的,真的会让人头大的

此demo的介绍

我原本做的是一个停车场的系统,现在由于数据量过多,需要将订单表拆分,而我这里的拆分逻辑是根据停车场id进行拆分,例如200个停车场就是200张订单表。
总的来说

分库逻辑:
按照创建时间的年份分库
分表逻辑:
按照停车场id进行分表

准备工作

数据库脚本

由于我们不是所有的表都要分表,所以数据库就有默认库,默认库是存放除了分表以外其他表的数据,具体配置见下面的详细步骤中的设置默认库
默认库:sharding2020

/*
Navicat MySQL Data Transfer

Source Server         : 开发数据库 4.71
Source Server Version : 50730
Source Host           : 192.168.4.71:3307
Source Database       : sharding2020

Target Server Type    : MYSQL
Target Server Version : 50730
File Encoding         : 65001

Date: 2020-05-08 11:05:41
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_car_park
-- ----------------------------
DROP TABLE IF EXISTS `t_car_park`;
CREATE TABLE `t_car_park` (
  `id` varchar(64) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='停车场表';

-- ----------------------------
-- Table structure for t_order_0
-- ----------------------------
DROP TABLE IF EXISTS `t_order_0`;
CREATE TABLE `t_order_0` (
  `id` varchar(64) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `car_park_id` varchar(64) DEFAULT NULL COMMENT '停车场id',
  `no` varchar(100) DEFAULT NULL COMMENT '订单号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试分表';

-- ----------------------------
-- Table structure for t_order_1
-- ----------------------------
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
  `id` varchar(64) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `car_park_id` varchar(64) DEFAULT NULL COMMENT '停车场id',
  `no` varchar(100) DEFAULT NULL COMMENT '订单号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试分表';

其中t_car_park就是停车场表,属于不用分表的业务表
第二个库:sharding2019

/*
Navicat MySQL Data Transfer

Source Server         : 开发数据库 4.71
Source Server Version : 50730
Source Host           : 192.168.4.71:3307
Source Database       : sharding2019

Target Server Type    : MYSQL
Target Server Version : 50730
File Encoding         : 65001

Date: 2020-05-08 11:06:06
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_order_0
-- ----------------------------
DROP TABLE IF EXISTS `t_order_0`;
CREATE TABLE `t_order_0` (
  `id` varchar(64) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `car_park_id` varchar(64) DEFAULT NULL COMMENT '停车场id',
  `no` varchar(100) DEFAULT NULL COMMENT '订单号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试分表';

-- ----------------------------
-- Table structure for t_order_1
-- ----------------------------
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
  `id` varchar(64) NOT NULL,
  `name` varchar(100) DEFAULT NULL COMMENT '名称',
  `car_park_id` varchar(64) DEFAULT NULL COMMENT '停车场id',
  `no` varchar(100) DEFAULT NULL COMMENT '订单号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试分表';

注意
这里有一个限制,如果配置了分库,则两个库以t_order_为开头的表,在两个库中的表结构表数量一定要是一样的,不然查询的时候shardingjdbc就会报错!!!

pom文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <!--sharding jdbc springboot-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>4.0.0-RC2</version>
        </dependency>


    </dependencies>

pom文件需要注意的问题:

  1. sharding-jdbc-spring-boot-starter以及sharding-jdbc-spring-namespace这里的版本用的是4.0.0-RC2,其他版本我也没试过,不知道有没有问题。
  2. 如果引用了sharingjdbc starter包,那么druid的包不能是starter的,只能是普通版本的
  3. 目前这个版本sharingjdbc包的版本好像和mybatisplus的版本有冲突,问题会在分页的时候出现,那就是分页的时候,汇总字段,会出现long类型转int的错误,经过测试,mybatis plus 版本选择3.3.1.tmp,sharding的版本选择4.0.0就不会出现这样的问题

application.yml

spring:
  shardingsphere:
    datasource:
      #  数据库名称,可自定义,可以为多个,以逗号隔开,每个在这里定义的库,都要在下面定义连接属性
      names: ds2019,ds2020
      #年份为2019年的库
      ds2019:
        #  采用的数据库连接池,druid
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.4.71:3307/sharding2019?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
        maxActive: 20
        initialSize: 5
        maxWait: 60000
        minIdle: 5
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        #是否缓存preparedStatement,也就是PSCache。在mysql下建议关闭。 PSCache对支持游标的数据库性能提升巨大,比如说oracle。
        poolPreparedStatements: false
        #要启用PSCache,-1为关闭 必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true  可以把这个数值配置大一些,比如说100
        maxOpenPreparedStatements: -1
        #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall,log4j2
        #通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        #合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
        loginUsername: druid
        loginPassword: druid
      #年份为2020年的库
      ds2020:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.4.71:3307/sharding2020?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
        maxActive: 20
        initialSize: 5
        maxWait: 60000
        minIdle: 5
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        #是否缓存preparedStatement,也就是PSCache。在mysql下建议关闭。 PSCache对支持游标的数据库性能提升巨大,比如说oracle。
        poolPreparedStatements: false
        #要启用PSCache,-1为关闭 必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true  可以把这个数值配置大一些,比如说100
        maxOpenPreparedStatements: -1
        #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall,log4j2
        #通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        #合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
        loginUsername: druid
        loginPassword: druid
    sharding:
      default-data-source-name: ds2020
      #需要拆分的表,可以设置多个
      tables:
        #需要进行分表的逻辑表名,用MyBatis或者MyBatis-Plus操作数据库时只需要操作逻辑表即可,xml文件也只需要配置逻辑表
        t_order:
          #实际的表结点,下面代表的是ds2019.t_order_0、ds2019.t_order_1、ds2020.t_order_0、ds2020.t_order_1 这几张表
          actual-data-nodes: ds$->{2019..2020}.t_order_$->{0..1}
          #分库策略,按照创建时间的年份分库,如果不用分库的,直接注释掉分库相关的代码
          database-strategy:
            standard:
              sharding-column: create_time
              precise-algorithm-class-name: com.example.demo.config.sharding.CreateTimeShardingDatabaseAlgorithm
          table-strategy:
            # 分表策略,根据自己的需要写的分表策略,这里我是根据car_park_id这个字段的值作为后缀,来确定放到哪张表
            standard:
              sharding-column: car_park_id
              precise-algorithm-class-name: com.example.demo.config.sharding.CarParkShardingTableAlgorithm
    props:
      # 是否打印逻辑SQL语句和实际SQL语句,建议调试时打印,在生产环境关闭
      sql:
        show: true
#是否输出Mybatis-Plus代执行的SQL语句
logging:
  level:
    com.example.demo.module.dao: trace
server:
  port: 8888

这里需要注意的问题

  1. database-strategy: 这个的意思是自定义分库策略,同样的table-strategy:这个代表的是自定义分表策略。而这和官方文档提供的分表分片策略有所不同,官方文档提供的是
    inline.png

    这个的意思是直接通过id取模分表,比如id为3,3%2 = 1,所以这条数据会被分配到t_order_1这种表中,这样的好处就是不用自己写配置类,比较方便,但是这种形式不够灵活,因为自己写分表配置类,也可以实现上面所说的功能。
    官方提供的分片策略有很多,可以支持自己定义,很灵活。
    可以参考:
    https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sharding/
    这里一定要记住关键字的区别:
    standard 代表自定义分表策略,需要配合precise-algorithm-class-name属性指定自定义分表策略所在的类
    inline 代表的是行表达式分片策略,可以通过简单的算法表达式直接进行分表

设置默认库
分了多个库,必须设置默认数据库!!!必须!!!不然普通业务表shardingjdbc不知道去哪个库取。
配置方式参考配置文件中的default-data-source-name: sharding2020
这里面填的数据库名字和上面配置的names必须是一样的

分库策略类

package com.example.demo.config.sharding;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;

/**
 * @title: CarParkShardingTableAlgorithm
 * @projectName shardingdemo
 * @description: 按创建时间分库
 * @author zhy
 * @date 2020/5/611:25
 */
public class CreateTimeShardingDatabaseAlgorithm implements PreciseShardingAlgorithm<Date> {


    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
        Date value = preciseShardingValue.getValue();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
        StringBuilder sb = new StringBuilder();
        sb.append("ds").append(sdf.format(value));
        return sb.toString();
    }
}

分表策略类

package com.example.demo.config.sharding;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;

/**
 * @title: CarParkShardingTableAlgorithm
 * @projectName shardingdemo
 * @description: 按停车场id分表
 * @author zhy
 * @date 2020/5/611:25
 */
public class CarParkShardingTableAlgorithm implements PreciseShardingAlgorithm<String> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
        StringBuilder sb = new StringBuilder();
        String value = preciseShardingValue.getValue();
        //获取设置的虚拟表名称,这里获取到的logicTableName=t_order
        String logicTableName = preciseShardingValue.getLogicTableName();
        //拼接实际的表名称,value为carParkId字段的值
        sb.append(logicTableName).append("_").append(value);
        return sb.toString();
    }
}

最后贴上源码地址,可以直接跑起来,看看效果,记得修改自己的数据库地址哦
https://github.com/zhyhuayong/shardingSpringbootDemo

存在的问题

这种方式适合表结构一定的方案,例如按月分表,每个月的表结构都能确定下来,也就是application.yml中的actual-data-nodes这个配置能确定下来,项目启动之后就不变,可以一直使用。但是问题来了,我这边的分表逻辑是按停车场id分表,停车场的是可以增删的,那么我现在分表的actual-data-nodes这个也要随着我停车场的改变而改变,所以这个问题困扰了我很久,最后我通过sharding-jdbc基于java的配置,加上ShardingSphere的服务编排治理,实现了在项目运行中动态的改变actual-data-nodes。详情请参考下一篇文章:https://www.jianshu.com/p/3254ac3bc414

本人为(weixin)恭(gong)祝(zhong)号:一吱小确幸。欢迎大家关注

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350