一、背景
公司领导安排研究一下Sharding-JDBC,用于项目的分表分库,因此有了这一篇文章,接下来分享ShardingSphere的整个学习步骤,有不正确的请指出。
二、工作准备
技术架构
SpringBoot2.2.4 + shardingsphere4.0.0-RC1 + Maven3.5.4 + MySQL8.0.11 + lombok(插件)
该项目是基于水平分表和分库
扩展:
- 垂直分表:按照字段使用的频率将一个完整的表按照字段分成多个表;
如:标题、内容等字段使用的频率较多,把表的结构拆分到不同的表中
- 垂直分库:专库专用,把不同类型的表分到不同的数据库;
如:系统模块涉及到的表分一个数据库,订单模块涉及到的表分到一个数据库
- 水平分表:按一定的规则将同一个表的数据按一定规则拆到不同的表中 ;
如:按照年纪为偶数的人分到A表,奇数的人分到B表
- 水平分库:同一个表的数据按一定规则拆到不同的数据库中 。
如:按照性别男的分到A数据库,女的分到B数据库
小结:
垂直分是将一个完整的表或者库进行结构的拆分,水平分是将数据行分发到不同的数据表或库中;
通常垂直分是在数据库设计之初就进行设计了,如:转库专用,大表(字段较多的表)拆成小表等。
三、环境搭建
1、创建项目
使用IDEA创建简单的Spring Boot的项目,选择如下骨架:
2、创建数据库表
CREATE TABLE `tab_user0` (
`id` bigint(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`sex` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '是否删除0未删除1删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `tab_user1` (
`id` bigint(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`sex` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '是否删除0未删除1删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
3、添加相关类
1、controller
package com.dyaqi.shardingsphere.controller;
import com.dyaqi.shardingsphere.entity.User;
import com.dyaqi.shardingsphere.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author Dyaqi
* @date 2021/1/8 9:48
* @功能描述: 接口测试
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
/**
* 批量保存用户
* @return
*/
@PostMapping("save-user")
public Object saveUser(@RequestBody List<User> userList) {
return userService.saveUser(userList);
}
/**
* 获取用户列表
* @return
*/
@GetMapping("list-user")
public Object getUserList() {
return userService.getUserList();
}
/**
* 获取用户
* @return
*/
@GetMapping("byid-user")
public Object getUserById(Long id) {
return userService.getUserById(id);
}
/**
* 删除用户
*/
@DeleteMapping
public String delUser(){
return userService.delUser();
}
}
2、service
package com.dyaqi.shardingsphere.service;
import com.dyaqi.shardingsphere.entity.User;
import java.util.List;
/**
* @author Dyaqi
* @date 2021/1/8 9:49
* @功能描述:
*/
public interface UserService {
Object saveUser(List<User> userList);
Object getUserList();
Object getUserById(Long id);
String delUser();
}
package com.dyaqi.shardingsphere.service.impl;
import com.dyaqi.shardingsphere.entity.User;
import com.dyaqi.shardingsphere.mapper.UserMapper;
import com.dyaqi.shardingsphere.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* @author Dyaqi
* @date 2021/1/8 9:49
* @功能描述:
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public Object saveUser(List<User> userList) {
for (User user : userList) {
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
user.setStatus(0);
userMapper.saveUser(user);
}
return "保存成功!";
}
@Override
public Object getUserList() {
return userMapper.getUserList();
}
@Override
public Object getUserById(Long id) {
return userMapper.getUserById(id);
}
@Override
public String delUser() {
userMapper.delUser();
return "OJBK";
}
}
3、mapper
package com.dyaqi.shardingsphere.mapper;
import com.dyaqi.shardingsphere.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author Dyaqi
* @date 2021/1/8 9:49
* @功能描述:
*/
@Mapper
public interface UserMapper {
void saveUser(User user);
List<User> getUserList();
User getUserById(Long id);
void delUser();
}
<?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="com.dyaqi.shardingsphere.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.dyaqi.shardingsphere.entity.User">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="sex" jdbcType="INTEGER" property="sex"/>
<result column="sexStr" jdbcType="VARCHAR" property="sexStr"/>
<result column="age" jdbcType="INTEGER" property="age"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="status" jdbcType="INTEGER" property="status"/>
</resultMap>
<sql id="Base_Column_List">
u.id,
u.name,
u.sex,
u.age,
u.create_time,
u.update_time,
u.status
</sql>
<insert id="saveUser" parameterType="com.dyaqi.shardingsphere.entity.User">
insert into tab_user (
name,
sex,
age,
create_time,
update_time,
status)
values
(
#{name},
#{sex},
#{age},
#{createTime},
#{updateTime},
#{status})
</insert>
<select id="getUserList" resultMap="BaseResultMap" parameterType="java.lang.Integer">
select
<include refid="Base_Column_List"/>
from tab_user u order by u.id
</select>
<select id="getUserById" resultMap="BaseResultMap" parameterType="java.lang.Long">
select
<include refid="Base_Column_List"/>,
d.label AS 'sexStr'
from tab_user u
left join sys_dict d on u.sex = d.value AND d.type = 'sex'
where u.id = #{id,jdbcType=BIGINT}
</select>
<delete id="delUser">
delete from tab_user
</delete>
</mapper>
4、user
package com.dyaqi.shardingsphere.entity;
import lombok.Data;
import java.util.Date;
/**
* @author Dyaqi
* @date 2021/1/8 9:50
* @功能描述: 用户表
*/
@Data
public class User {
/**
* 主键
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private Integer sex;
/**
* 性别
*/
private String sexStr;
/**
* 年龄
*/
private Integer age;
/**
*
*/
private Date createTime;
/**
*
*/
private Date updateTime;
/**
*
*/
private Integer status;
public User() {
}
public User(Long id, String name, Integer sex, Integer age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
三、实现分表功能
1、目的
要实现的是tab_user的分表,将tab_user中id为偶数的分发到tab_user0中,奇数的分发到tab_user1中。
2、配置pom文件
添加关于sharding-jdbc的坐标
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.0.0-RC1</version>
</dependency>
3、配置properties文件
添加数据库信息
server.port=80
#指定mybatis信息
mybatis.config-location=classpath:mybatis-config.xml
spring.shardingsphere.datasource.names=ds0
# 配置数据源
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/demo0?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
#配置数据节点
spring.shardingsphere.sharding.tables.tab_user.actual-data-nodes=ds0.tab_user$->{0..1}
#指定主键
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.sharding-column=id
#配置分表规则:主键除以2取模
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.algorithm-expression=tab_user$->{id % 2}
#打印sql
spring.shardingsphere.props.sql.show=true
#Bean覆盖被允许
spring.main.allow-bean-definition-overriding=true
注:
其中配置中的tab_user即为要分的表,此处也可修改为abc
,但在编写SQL时,操作的表名就要变为abc
。
分表规则解释:当id为偶数时,id % 2 表达式的结果为0,则表达式结果就会取代$,得到结果表示为tab_user0,此时指定的为tab_user0表。
4、配置mybatis-config文件
指定mybatis的关系
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.dyaqi.shardingsphere"/>
</typeAliases>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
5、测试分析
调用新增接口(http://localhost/save-user ) 发送如下数据,则数据被分发到两张表中,水平分表实现。
[
{
"id": 1,
"name": "小小",
"sex": 0,
"age": 14
},
{
"id": 2,
"name": "爸爸",
"sex": "男",
"age": 45
},
{
"id": 3,
"name": "妈妈",
"sex": 0,
"age": 43
},
{
"id": 4,
"name": "爷爷",
"sex": 0,
"age": 61
},
{
"id": 5,
"name": "奶奶",
"sex": 0,
"age": 62
}
]
四、实现分库分表
1、目的
要实现将女性成员发送到demo0库,男性成员发送到demo1库,同时将tab_user中id为偶数的分发到tab_user0中,奇数的分发到tab_user1中。
2、配置properties文件
以下信息为和分表有不同的配置
# 配置真实数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 配置数据源
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/demo0?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/demo1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
#配置数据节点
spring.shardingsphere.sharding.tables.tab_user.actual-data-nodes=ds$->{0..1}.tab_user$->{0..1}
#根据性别分库
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=sex
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{sex % 2}
#根据id分表
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.algorithm-expression=tab_user$->{id % 2}
注:
数据节点:着重注意;
分库时,和分表逻辑一致。
3、测试分析
测试的数据还用上边的;
按照目的,我们可猜测结果为:demo0库中的tab_user1表有三条记录小小、妈妈、奶奶,demo1库中tab_user0表有两条记录爸爸、爷爷。
五、加入分布式主键
1、目的
在实际操作中id是系统自动生成的并非前端传递的,因此要利用Sharding-Jdbc的配置去实现分布式主键生成。
2、配置properties文件
Sharding-Jdbc支持的分布式主键有两种,雪花算法(Long型)和UUID(String型)。
# 使用雪花算法作为默认id
spring.shardingsphere.sharding.tables.tab_user.key-generator.column=id
spring.shardingsphere.sharding.tables.tab_user.key-generator.type=SNOWFLAKE
3、测试分析
六、加入广播表和绑定表
1、目的
在真实的项目中,会有部分表数据量少,但是又经常会在关联查询中使用到,例如字典表。因此在demo0和demo1数据库中都存在sys_dict表,用于关联查询。
2、SQL
CREATE TABLE `sys_dict` (
`id` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '编号',
`value` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '数据值',
`label` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '标签名',
`type` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '类型',
`description` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '描述',
`sort` decimal(10, 0) NOT NULL COMMENT '排序(升序)',
PRIMARY KEY (`id`) USING BTREE,
INDEX `sys_dict_value`(`value`) USING BTREE,
INDEX `sys_dict_label`(`label`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '字典表' ROW_FORMAT = Dynamic;
INSERT INTO `sys_dict` VALUES ('1', '1', '男', 'sex', '性别', 1);
INSERT INTO `sys_dict` VALUES ('2', '0', '女', 'sex', '性别', 2);
结果为:
3、配置properties文件
#广播表, 其主节点是ds0
spring.shardingsphere.sharding.broadcast-tables=sys_dict
4、测试分析
在获取详情接口http://localhost/byid-user 中,存在关联查询,查询后结果如下:
七、不分表的设置
1、目的
在真实使用中,有一部分表不与拆分表有任何关系,没有必要将该表进行配置,经过配置后按照普通方式调用即可。
2、SQL
CREATE TABLE `region` (
`id` bigint(20) NOT NULL COMMENT 'id',
`region_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地理区域编码',
`region_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地理区域名称',
`level` tinyint(1) NULL DEFAULT NULL COMMENT '地理区域级别(省、市、县)',
`parent_region_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上级地理区域编码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `region` VALUES (1, '110000', '北京', 0, NULL);
INSERT INTO `region` VALUES (2, '410000', '河南省', 0, NULL);
INSERT INTO `region` VALUES (3, '110100', '北京市', 1, '110000');
INSERT INTO `region` VALUES (4, '410100', '郑州市', 1, '410000');
结果为:
3、配置properties文件
# 设置默认的数据库,不分表的默认从该表查询
spring.shardingsphere.sharding.default-data-source-name=ds0
4、测试分析
在http://localhost/region 接口中,只查询region表,查询后结果如下:
八、ShardingSphere官网
本文章只介绍这么多,更多内容请见官网:https://shardingsphere.apache.org/document/current/cn/overview/ ,若需要项目中完整的代码请留言。