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決策使用的表
採用策略:數據庫:tpdbtestuser{db} 表:test_user{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 數據源 操作數據庫 操作表
0 1 ds0 tp test_user1
0 2 ds0 tp test_user0
1 3 ds1 tp1 test_user1
1 4 ds1 tp1 test_user0

讀寫分離+分庫分表用法

相對與讀寫分離、分庫分表而言,pom無需變化
讀slave庫,根據db決策查詢的數據庫,根據sex%2決策查詢的表
寫master庫,根據db決策寫入的數據庫,根據sex%2決策寫入的表
採用策略:數據庫:tpdbtestuser{db} 表:test_user{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
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章