04、Sharding-Sphere 实战:垂直分库,实现写入读取

概述:垂直分库即转库专用,不同的数据库中存放不同的表信息。比如学生和课程信息,我们将课程表存放与一个数据库中,学生信息存储于另一个库中,本章将介绍如何通过配置Sharding jdbc实现垂直分库操作。

环境:SpringBoot 2.2 + mybatis plus3.0 + Sharding jdbc4.0

需求:我们将用户表t_user 表存放在数据库user_db中。course表保持上一章节《03、Sharding-Sphere 实战:水平分库,实现写入读取》中的水平分库水平分表配置。我们在保存读取用户信息时在user_db库中操作,读写course表时在course_db_1、course_db_2表中操作。

工程目录结构:

 

主要步骤:

1、 数据库环境准备;
2、 创建maven工程引入依赖;
3、 创建主启动类;
4、 创建业务代码;
5、 创建SpringBoot配置文件;
6、 编写测试类测试验证;

一、数据库环境准备

分表创建三个数据库 user_db、course_db_1、course_db_2。user_db库中创建t_user表,course_db12中分别创建course_1、course_2表

#课程表 course_1 course_2 结构一致
CREATE TABLE course_1 (
  cid bigint(20) NOT NULL,
  cname varchar(50) NOT NULL,
  user_id bigint(20) NOT NULL,
  cstatus varchar(10) NOT NULL,
  PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
#用户表
CREATE TABLE t_user (
  user_id bigint(20) NOT NULL,
  username varchar(50) NOT NULL,
  ustatus varchar(50) NOT NULL,
  PRIMARY KEY (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

二、创建maven工程

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.sharding</groupId>
    <artifactId>sharding</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.31</version>
        </dependency>

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

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

三、创建主启动类

src/main/java/com/xiaohui/ShardingApplication.java

package com.xiaohui;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.xiaohui.mapper")
public class ShardingApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingApplication.class,args);
    }
}

四、创建业务代码

编写数据库实体类以及表Dao层接口类,注意mybatis-plus 中接口层需要继承BaseMapper接口

src/main/java/com/xiaohui/entity/Course.java

package com.xiaohui.entity;

import lombok.Data;

@Data
public class Course {
    private Long cid;
    private String cname;
    private Long userId;
    private String cstatus;
}

由于user实体类与表名不匹配,所以需要使用mybatis-plus @TableName注解进行声明

package com.xiaohui.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName(value="t_user")
public class User {
    private Long userId;
    private String username;
    private String ustatus;
}

src/main/java/com/xiaohui/mapper/CourseMapper.java

package com.xiaohui.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaohui.entity.Course;
import org.springframework.stereotype.Repository;

@Repository
public interface CourseMapper extends BaseMapper<Course> {
}

src/main/java/com/xiaohui/mapper/UserMapper.java

package com.xiaohui.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaohui.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

五、创建SpringBoot 配置文件(重点)

创建SpringBoot配置文件 application.properties

#sharding-jdbc 分片策略
#=================================数据源配置部分========================================
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names=ds0,ds1,ds2
#配置 ds0 ds1数据源具体内容
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://127.0.0.1:3306/course_db_1
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://127.0.0.1:3306/course_db_2
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds2.url=jdbc:mysql://127.0.0.1:3306/user_db
spring.shardingsphere.datasource.ds2.username=root
spring.shardingsphere.datasource.ds2.password=root

#=================================数据节点配置部分======================================
#配置course表在数据库上的信息,在那些数据库里,以及库中的分表情况(tables 后面的course可以随便取名代表后面的匹配表)
spring.shardingsphere.sharding.tables.course.actual-data-nodes=ds$->{0..1}.course_$->{1..2}
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=ds$->{2}.t_user

#=================================数据库分库策略=======================================
# 配置默认的水平分库的策略 使用 user_id字段进行判断
#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
#数据库策略
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.algorithm-expression=ds2
# 配置course表的分库策略
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}

#=================================数据库分表策略========================================
#指定分片策略 约定cid值偶数添加到couse_1表,奇数添加到course_2表上
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 +1}
#表策略table-strategy
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user

#==================================数据库字段生成策略====================================
###单表的水平拆分配置 指定course表里面主键cid的生成策略 SNOWFKAKE 雪花算法
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
#字段生成策略key-generator
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE

#==================================其他配置=============================================
#解决报错 Consider renaming one of the beans or enabling overriding...
spring.main.allow-bean-definition-overriding=true
#打开sql日志输出
spring.shardingsphere.props.sql.show=true

在垂直分库配置中主要声明部分在 数据节点配置中需要指定对应表所在的库。以及分库策略中配置对应的分库策略,以来实现专库专用。

spring.shardingsphere.sharding.tables.course.actual-data-nodes=ds$->{0..1}.course_$->{1..2}
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=ds$->{2}.t_user
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.algorithm-expression=ds2
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}

我们t_user表只有一个库所以我们在配置inline的分库表达式就直接写死为ds2库。

六、编写测试类测试验证

编写测试类:src/test/java/com/xiaohui/MainTest.java

package com.xiaohui;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaohui.entity.Course;
import com.xiaohui.entity.User;
import com.xiaohui.mapper.CourseMapper;
import com.xiaohui.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MainTest {

    @Autowired
    private CourseMapper courseMapper;

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testUserAdd(){
        User u = new User();
        u.setUsername("王五");
        u.setUstatus("1");
        userMapper.insert(u);
    }
       @Test
    public void testUserGet(){
        QueryWrapper wrapper = new QueryWrapper<User>();
        wrapper.eq("user_id", 563316786842304513L);
        User user = userMapper.selectOne(wrapper);
        System.out.println(user);
    }

    @Test
    public void testCourseGet(){
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        wrapper.eq("cid",563102169142657024L);
        wrapper.eq("user_id",102L);
        Course course = courseMapper.selectOne(wrapper);
        System.out.println(course);
    }
    @Test
    public void testCourseDel(){
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        wrapper.eq("cid",557605627916976129L);
        wrapper.eq("user_id",101L);
        courseMapper.delete(wrapper);
    }
}

  • 执行新增测试方法testUserAdd(),控制台打印如下:
2023-02-02 10:59:47.944  INFO 8960 --- [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO t_user  ( username,ustatus )  VALUES  ( ?,? )
2023-02-02 10:59:47.945  INFO 8960 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds2 ::: INSERT INTO t_user   (username, ustatus, user_id) VALUES (?, ?, ?) ::: [王五, 1, 563316786842304513]

通过sql 和数据库查看我们只在ds2即 user_db库中新增了一条数据。

执行查询用户测试方法testUserGet(),日志打印如下:

2023-02-02 11:08:57.926  INFO 8780 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT  user_id,username,ustatus  FROM t_user  WHERE  user_id = ?
2023-02-02 11:08:57.927  INFO 8780 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds2 ::: SELECT  user_id,username,ustatus  FROM t_user  WHERE  user_id = ? ::: [563316786842304513]
User(userId=563316786842304513, username=王五, ustatus=1)

  • 执行课程查询方法,日志打印如下:
2023-02-02 11:12:56.141  INFO 7824 --- [           main] ShardingSphere-SQL                       : Rule Type: sharding
2023-02-02 11:12:56.143  INFO 7824 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT  cid,cname,user_id,cstatus  FROM course  WHERE  cid = ? AND user_id = ?
2023-02-02 11:12:56.144  INFO 7824 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds0 ::: SELECT  cid,cname,user_id,cstatus  FROM course_1  WHERE  cid = ? AND user_id = ? ::: [563102169142657024, 102]
Course(cid=563102169142657024, cname=sql_2, userId=102, cstatus=1)

根据上面打印,均按照Sharding对应的配置规则进行正确的分库分表路由。成功的进行了新增以及查询操作。