Springboot基于shardingsphere实现分库分表及读写分离

代码地址

环境-版本

Springboot:2.2.5.RELEASE
com.alibaba:druid-spring-boot-starter:1.1.21
io.shardingsphere:sharding-jdbc-spring-boot-starter:3.1.0

基础环境

数据库权限:

用户tp数据库(查)tp数据库(增)tp1数据库(查)tp1数据库(增)tp2数据库(查)tp2数据库(增)
tp××××
tp1×××××
tp2×××××

数据库表结构:
tp、tp1、tp2数据下的test_user、test_user0、test_user1结构均一样。
在这里插入图片描述
项目结构预览
项目结构预览
基于shardingsphere,无论是实现的分库分表还是读写分离,对业务代码均没有侵入,因此这里先把基础的代码列出
application.yml

spring:
  profiles:
    #[单库单表]
    #active: single
    #[读写分离]
    active: masterslave
    #[分库分表]
    #active: strategy
    #[读写分离+分库分表]
    #active: masterslavestrategy
  main:
    allow-bean-definition-overriding: true
server:
  servlet:
    context-path: /tp
  port: 8806

mybatis:
  mapper-locations: classpath:mapping/*Mapper.xml
  type-aliases-package: cn.com.test.sharding.model


log4j:
  rootLogger: debug,stdout
  config: classpath:log4j2.xml

实体TestUser.java

package cn.com.test.sharding.model;

import com.alibaba.fastjson.JSON;

import java.util.Date;

public class TestUser {
    public Integer id;
    public String db;
    public String userId;
    public Integer sex;
    public String remark;
    public Date modifyDate;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getDb() {
        return db;
    }

    public void setDb(String db) {
        this.db = db;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getModifyDate() {
        return modifyDate;
    }

    public void setModifyDate(Date modifyDate) {
        this.modifyDate = modifyDate;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

Dao操作接口TestUserMapper.java

package cn.com.test.sharding.dao;

import cn.com.test.sharding.model.TestUser;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface TestUserMapper {
    void insert(TestUser user);

    List<TestUser> list();

    List<TestUser> listByremark(String remark);

    List<TestUser> listByremarkLimit2(String remark);
}

SQL操作 TestUserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.com.test.sharding.dao.TestUserMapper">
    <resultMap id="baseResultMap" type="cn.com.test.sharding.model.TestUser">
        <id column="id" jdbcType="INTEGER" property="id"/>
        <id column="db" jdbcType="VARCHAR" property="db"/>
        <result column="user_id" jdbcType="VARCHAR" property="userId"/>
        <result column="sex" jdbcType="INTEGER" property="sex"/>
        <result column="remark" jdbcType="VARCHAR" property="remark"/>
        <result column="modify_time" jdbcType="TIMESTAMP" property="modifyDate"/>
    </resultMap>
    <sql id="base_column_list">
        id,db,user_id,sex,remark,modify_time
    </sql>
    <insert id="insert" parameterType="cn.com.test.sharding.model.TestUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into test_user (
            <include refid="base_column_list"/>
        )
        value (
         #{id,jdbcType=INTEGER},#{db,jdbcType=VARCHAR},#{userId,jdbcType=VARCHAR},#{sex,jdbcType=INTEGER},#{remark,jdbcType=VARCHAR},current_timestamp
        );
    </insert>
    <select id="list" resultMap="baseResultMap">
        select
        <include refid="base_column_list"/>
        from test_user
        where 1=1

    </select>
    <select id="listByremark" resultMap="baseResultMap" parameterType="string">
        select
        <include refid="base_column_list"/>
        from test_user
        where remark like #{remark,jdbcType=VARCHAR}
    </select>
    <select id="listByremarkLimit2" resultMap="baseResultMap" parameterType="string">
        select
        <include refid="base_column_list"/>
        from test_user
        where remark like #{remark,jdbcType=VARCHAR}
        limit 2
    </select>
</mapper>

操作层 TestController.xml

package cn.com.test.sharding.controller;

import cn.com.test.sharding.dao.TestUserMapper;
import cn.com.test.sharding.model.TestUser;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/test")
public class TestController {
    static Logger logger = LoggerFactory.getLogger(TestController.class);
    @Autowired
    TestUserMapper testUserMapper;
    @GetMapping("/user")
    public String user(TestUser user){
        logger.info("请求:"+user);
        user.setUserId(UUID.randomUUID().toString());
        testUserMapper.insert(user);
        logger.info(user.toString());
        List<TestUser> userList = testUserMapper.list();
        logger.info("all查询结果:"+userList.size()+"\t"+JSON.toJSONString(userList));
        List<TestUser> userList1 = testUserMapper.listByremark(user.getRemark()+"%");
        logger.info("remark查询结果:"+userList1.size()+"\t"+JSON.toJSONString(userList1));
        List<TestUser> userList2 = testUserMapper.listByremarkLimit2(user.getRemark()+"%");
        logger.info("remark查询(限定查询2条)结果:"+userList2.size()+"\t"+JSON.toJSONString(userList2));
        return JSON.toJSONString(userList);
    }

}

常规用法

常规用法,既不分库也不分表;读写在一个库上进行

application-single.yml配置:

# 不读写分离也不分表分库
spring:
  servlet:
    multipart:
      max-file-size: 1MB
      max-request-size: 2MB
  datasource:
    username: tp
    password: tp@123
    url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 2
    minIdle: 2
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUALco
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    druid.filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 2

pom.xml引入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.test</groupId>
    <artifactId>sprintboot-sharding</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>sharding-demon</name>
    <description>Spring-boot sharding</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </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>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <!--fork :  如果没有该项配置,肯定devtools不会起作用,即应用不会restart -->
                    <fork>true</fork>
                    <excludeDevtools>true</excludeDevtools>
                    <excludes>
                        <exclude>
                            <artifactId>spring-boot-starter-tomcat</artifactId>
                            <groupId>org.springframework.boot</groupId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

常规情况

测试url:localhost:8806/tp/test/user?sex=0&remark=DD&db=0
分析:
这种情况比较容易理解,数据库操作对象清晰可见,数据库:tp,表:test_user

测试url:http://localhost:8806/tp/test/user?sex=0&remark=DD&db=0
代码执行结果:
在这里插入图片描述
数据库:
在这里插入图片描述

读写分离用法

读写在不同的数据库上
采用策略:在tp上写,在tp1上读 表:test_user
预期结果:tp上数据正常写入,tp1读取到测试数据

application-masterslave.yml配置:

sharding:
  jdbc:
    datasource:
      names: master,slave
      master:
        username: tp
        password: tp@123
        url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      slave:
        username: tp1
        password: tp1@123
        url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    config:
      # 主从库配置 操作表名不会跟上数字(test_user)
      masterslave:
        # 负载均衡策略(load-balance-algorithm-class-name:  指定自定义策略,实现MasterSlaveLoadBalanceAlgorithm且提供无参构造)
        load-balance-algorithm-type: random
        master-data-source-name: master
        slave-data-source-names: slave
        name: demo1
      # 读写分离的实验结果:读库没有数据,所以数据可以正常写入,但是读出结果为空

执行测试前:
tp:
在这里插入图片描述
tp1:
在这里插入图片描述
执行结果:
在这里插入图片描述
tp:
在这里插入图片描述
tp1

在这里插入图片描述

分库分表用法

相对与读写分离而言,pom无需变化
无论读写,根据db决策使用的数据库,根据sex%2决策使用的表
采用策略:数据库:tp d b 表 : t e s t u s e r {db} 表:test_user dbtestuser{sex % 2}
预期结果:

{db=0,sex=1}写入tp.test_user1
{db=0,sex=2}写入tp.test_user0
{db=1,sex=3}写入tp1.test_user1
{db=1,sex=4}写入tp1.test_user0

application-strategy.yml

spring:
  main:
    allow-bean-definition-overriding: true
sharding:
  jdbc:
    datasource:
      names: ds0,ds1
      ds0:
        username: tp
        password: tp@123
        url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      ds1:
        username: tp1
        password: tp1@123
        url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    config:
      sharding:
        props:
          sql.show: true
        #分表分库策略 操作带数字的表和数据源ds[0,1].test_user[0,1]
        tables:
          test_user:
            # 自增主键,这里声明后mapper.xml中返回主键不再可用
            # key-generator-column-name: id
            # 数据节点,均匀分布 单一数据源去除数据库后的表达式即可 eg: ds$.test_user${0..1}
            actual-data-nodes: ds${0..1}.test_user${0..1}
            #分库策略
            database-strategy:
              #行表达式
              inline:
                #列名称,多个列以逗号分隔
                sharding-column: db
                #直接参数里指定了数据源
                algorithm-expression: ds${db}
            #分表策略
            table-strategy:
              #行表达式
               inline:
                 #分片列
                 sharding-column: sex
                 #分片表达式 对sex列取模
                 algorithm-expression: test_user${sex % 2}
               #自定义复杂分片
#               complex:
#                 sharding-columns: #分片列名称,多个列以逗号分隔
#                 algorithm-class-name: #复合分片算法类名称。实现ComplexKeysShardingAlgorithm并提供无参数的构造器
        #分库分表,根据db决策写入的数据库,根据sex决策写入的表,读取时应为所有来源之和

{db=0,sex=1}写入tp.test_user1

测试url:localhost:8806/tp/test/user?sex=1&remark=DD&db=0
清空tp、tp1下的test_user0、test_user1

执行结果
在这里插入图片描述tp.test_user1
在这里插入图片描述

{db=0,sex=2}写入tp.test_user0

测试url:localhost:8806/tp/test/user?sex=2&remark=DD&db=0
执行结果(汇总上一种的情况进行查询,所以查询结果应该为2条):
在这里插入图片描述tp.test_user0
在这里插入图片描述

{db=1,sex=3}写入tp1.test_user1

测试url:localhost:8806/tp/test/user?sex=3&remark=DD&db=1
执行结果(汇总上一种的情况进行查询,所以查询结果应该为3条):
在这里插入图片描述
tp1.test_user1
在这里插入图片描述

{db=1,sex=4}写入tp1.test_user0

测试url:localhost:8806/tp/test/user?sex=4&remark=DD&db=1
执行结果(汇总上一种的情况进行查询,所以查询结果应该为4条):
在这里插入图片描述在这里插入图片描述简单整理下对应关系~

条件db条件sex数据源操作数据库操作表
01ds0tptest_user1
02ds0tptest_user0
13ds1tp1test_user1
14ds1tp1test_user0

读写分离+分库分表用法

相对与读写分离、分库分表而言,pom无需变化
读slave库,根据db决策查询的数据库,根据sex%2决策查询的表
写master库,根据db决策写入的数据库,根据sex%2决策写入的表
采用策略:数据库:tp d b 表 : t e s t u s e r {db} 表:test_user dbtestuser{sex % 2}
增加tp2并仅对tp2赋予对tp2库所有表的查询权限
预期结果:

{db=0,sex=5}写入tp.test_user1 读取tp2两次
{db=0,sex=6}写入tp.test_user0 读取tp2两次
{db=1,sex=7}写入tp1.test_user1 读取tp2两次
{db=1,sex=8}写入tp1.test_user0 读取tp2两次
注意:db被拆分为两个表,所以读取和写入均在两个表之间决策和聚集

application-masterslavestrategy

sharding:
  jdbc:
    datasource:
      names: master0,master1,master0-slave,master1-slave
      master0:
        username: tp
        password: tp@123
        url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      master1:
        username: tp1
        password: tp1@123
        url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      master0-slave:
        username: tp2
        password: tp2@123
        url: jdbc:mysql://192.168.1.16:3308/tp2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      master1-slave:
        username: tp2
        password: tp2@123
        url: jdbc:mysql://192.168.1.16:3308/tp2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    config:
      sharding:
        tables:
          test_user:
            # 自增主键,这里声明后mapper.xml中返回主键不再可用
            # key-generator-column-name: id
            # 数据节点,均匀分布 单一数据源去除数据库后的表达式即可 eg: ds$.test_user${0..1}
            actual-data-nodes: ds${0..1}.test_user${0..1}
            #分库策略
            database-strategy:
              #行表达式
              inline:
                #列名称,多个列以逗号分隔
                sharding-column: db
                #直接参数里指定了数据源
                algorithm-expression: ds${db}
            #分表策略
            table-strategy:
              #行表达式
              inline:
                #分片列
                sharding-column: sex
                #分片表达式 对sex列取模
                algorithm-expression: test_user${sex % 2}
        master-slave-rules:
            ds0:
                master-data-source-name: master0
                slave-data-source-names: master0-slave
            ds1:
              master-data-source-name: master1
              slave-data-source-names: master1-slave

测试过程省略,执行结果如下:
在这里插入图片描述
tp.test_user0
在这里插入图片描述
tp.test_user1
在这里插入图片描述
tp1.test_user0
在这里插入图片描述
tp1.test_user1
在这里插入图片描述tp2.test_user0
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408215603959.png
tp2.test_user1
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值