目录
1:ShardingSphere实现Spring的读写分离分库分表
4.3:配置垂直拆分conf下的config-sharding.yaml
1:ShardingSphere实现Spring的读写分离分库分表
参见ShadingSphere官网
本项目基于jdk17、springboot3.1.2、sharding5.3.2、mysql8.0.15
版本问题很严重,在测试过程中存在很多冲突 ,一定要找到ShadingSphere的具体版本来进行测试
2:什么是ShardingSphere
Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
可以实现读写分离和分库分表
Apache ShardingSphere 由 ShardingSphere-JDBC 和 ShardingSphere-Proxy 这 2 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景,他们都能实现分库分表。
ShardingSphere-JDBC:ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。

ShardingSphere-Proxy:ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。 目前提供 MySQL 和 PostgreSQL 协议,透明化数据库操作,对 DBA 更加友好。

3:ShardingJDBC实现代码案例
3.1:pom准备
<!--
springboot3.0以上的版本,
可能只有ShardingSphere 5.3.x支持。
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 5.3.2版本不在依赖SpringBoot-starter 直接导入shardingsphere-jdbc-core就行-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.3.2</version>
</dependency>
<!--MybatisPlus的jar 3.0基于jdk8-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!--直接使用druid的starter-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- <version>1.2.16</version>-->
<!-- </dependency>-->
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
</dependencies>
3.2:读写分离
业务场景复现:三个数据库中分别有相同的表,分为1主2从,写数据去主库,查数据到从库
1:配置application.properties,通过切换yml实现不同的功能
spring.application.name=shardingJdbc_demo1
#开发环境
spring.profiles.active=dev
# 运行模式类型。可选配置:Memory、Standalone、Cluster
spring.shardingsphere.mode.type=Memory
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定YAML配置文件 sharding_水平多表分库分片可以进行切换
spring.datasource.url=jdbc:shardingsphere:classpath:sharding_读写分离.yaml
2:配置sharding_读写分离.yaml
#读写分离测试配置 主数据配置:一个主库 二个从库
#查询走从库新增主库
#在这三个数据库中 都有表TestUser
dataSources:
# 主库写
master:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
slave1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
slave2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
#规则配置
rules:
- !READWRITE_SPLITTING
dataSources:
readwrite_ds:
staticStrategy:
# 写库数据源名称
writeDataSourceName: master
# 读库数据源名称 多个可以分割
readDataSourceNames:
- slave1
- slave2
loadBalancerName: random
# 负载均衡算法配置
loadBalancers:
# loadBalancerName 由用户指定,需要和读写分离规则中的 loadBalancerName 属性一致
random:
#ROUND_ROBIN 轮训算法 RANDOM 随机算法 WEIGHT权重算法
type: ROUND_ROBIN
# props:
# slave1: 1
# slave2: 1
# 配置其他数据源
props:
sql-show: true
3:实体entity(在这三个数据库中创建TestUser表)
/**
*
* @TableName TestUser
*/
@TableName(value ="TestUser")
@Data
public class Testuser implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String address;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
4:测试类
@SpringBootTest
class 读写分离Test {
@Autowired
TestuserMapper testuserMapper;
/**
* 查询数据库操作 分别去从库1和从库2中轮训查询数据
* 负载均衡
*/
@Test
void select() {
Testuser testUser2 = testuserMapper.selectById(1);
System.out.println(testUser2);
Testuser testUser3 = testuserMapper.selectById(1);
System.out.println(testUser3);
Testuser testUser22 = testuserMapper.selectById(1);
System.out.println(testUser22);
Testuser testUser33 = testuserMapper.selectById(1);
System.out.println(testUser33);
}
/**
* 查询数据库操作 分别去从库1和从库2中轮训查询数据
* 负载均衡
*/
@Test
void selectAll() {
List<Testuser> users = testuserMapper.selectList(null);
users.forEach(System.out::println);
}
/**
* 添加数据到主库
* 没有事务 从库查询
*/
@Test
void add() {
Testuser testuser1 = new Testuser();
testuser1.setName("aaa");
testuser1.setAddress("a地址");
testuserMapper.insertUser(testuser1);
System.out.println("主键:"+testuser1.getId());
Testuser testuser2 = new Testuser();
testuser2.setName("bbb");
testuser2.setAddress("b地址");
testuserMapper.insertUser(testuser2);
System.out.println("主键:"+testuser2.getId());
//查询数据主库
List<Testuser> users = testuserMapper.selectList(null);
users.forEach(System.out::println);
}
/**
* 添加数据到主库
* 添加事务 第二条数据address过长
* 两条数据都不会插入成功
*
* 主库查询
* junit的@Transactional 默认进行回滚 这里进行验证
*/
@Transactional
@Test
void add事务() {
Testuser testuser1 = new Testuser();
testuser1.setName("事务1");
testuser1.setAddress("a地址");
testuserMapper.insertUser(testuser1);
System.out.println("主键:"+testuser1.getId());
Testuser testuser2 = new Testuser();
testuser2.setName("事务2");
testuser2.setAddress("b地址");// address=b地址阿发手动阀是的撒发生的方法打法上
testuserMapper.insertUser(testuser2);
System.out.println("主键:"+testuser2.getId());
List<Testuser> users = testuserMapper.selectList(null);
users.forEach(System.out::println);
}
}
3.3:垂直分库分表
业务场景复现:一个业务大表垂直分成拆分,分成两个或者多个小表,在不同的数据库中
1:配置application.properties,通过切换yml实现不同的功能
spring.application.name=shardingJdbc_demo1
#开发环境
spring.profiles.active=dev
# 运行模式类型。可选配置:Memory、Standalone、Cluster
spring.shardingsphere.mode.type=Memory
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定YAML配置文件 sharding_水平多表分库分片可以进行切换
spring.datasource.url=jdbc:shardingsphere:classpath:sharding_垂直分片.yaml
2:配置sharding_垂直分片.yaml
#垂直分片,一个表属性太多,进行垂直分片
#将一个大表 垂直拆分到两个数据库W1(TestUser) W11(Orders)
dataSources:
# 主库写
master1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
master2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W11?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
rules:
- !SHARDING
tables:
#表名
TestUser:
#绑定数据库的表名
actualDataNodes: master1.TestUser
#表名
orders:
#绑定数据库的表名
actualDataNodes: master2.orders
#日志开启
props:
sql-show: true
3:实体entity(在这两个数据库中创建TestUser、orders表)
/**
*
* @TableName TestUser
*/
@TableName(value ="TestUser")
@Data
public class Testuser implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String address;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
@TableName(value = "orders")
@Data
public class Order implements Serializable {
/**
* 主键
* //默认主键自增
* //type =IdType.AUTO 主键必须设置自增 获取插入的主键值
* //type =IdType.NONE 默认值 获取不到主键
* //type =IdType.ASSIGN_UUID uuid不能是Int类型
* //IdType.ASSIGN_ID 雪花算法设置生成id 主键不能为递增
*/
// @TableId(type = IdType.AUTO)
// private int id;
//水平分片 雪花算法
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
//使用 keyGenerateStrategy 需要设置ONE
@TableId(type = IdType.NONE)
private Long id;
private Integer userId;//根据userid分库
private String orderNo;//根据order分表
private BigDecimal amount;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
4:测试类
package org.example.springboot_2_shardingjdbc;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.example.springboot_2_shardingjdbc.entity.Order;
import org.example.springboot_2_shardingjdbc.entity.Testuser;
import org.example.springboot_2_shardingjdbc.entity.User;
import org.example.springboot_2_shardingjdbc.service.OrderService;
import org.example.springboot_2_shardingjdbc.service.TestuserService;
import org.example.springboot_2_shardingjdbc.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@SpringBootTest
class 垂直分片2Test {
@Autowired
TestuserService testuserService;
@Autowired
OrderService orderService;
@Autowired
UserService userService;
/**
* 将一个大表 垂直拆分到两个数据库
* W1(TestUser)
* W11(Orders)
* 查询两个表的数据 到不同的库下边的表
*/
@Test
//@Transactional
void select1() {
//查询Testuser 路由到master1
Testuser testuser1 = testuserService.getById(1);
Testuser testuser2 = testuserService.getById(1);
System.out.println(testuser1);
System.out.println(testuser2);
System.out.println("========"+(testuser1==testuser2));
//查询orders 路由到master2
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",testuser1.getId());
Order order1=orderService.getOne(queryWrapper);
Order order2=orderService.getOne(queryWrapper);
System.out.println(order1);
System.out.println(order2);
}
/**
* 查询其他的表,查询master1
*
*/
/**
* 将一个大表 垂直拆分到两个数据库W1(TestUser) W11(Orders)
* 查询两个表的数据 到不同的库下边的表
*
* 重点:查询其他的表,没有配置规则咋办,查询master1
*/
@Test
void select其他表() {
//查询Testuser 路由到master1
Testuser testuser1 = testuserService.getById(1);
Testuser testuser2 = testuserService.getById(1);
System.out.println(testuser1);
System.out.println(testuser2);
System.out.println("========");
//查询orders 路由到master2
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",testuser1.getId());
Order order1=orderService.getOne(queryWrapper);
Order order2=orderService.getOne(queryWrapper);
System.out.println(order1);
System.out.println(order2);
//查询其他的表,没有配置规则 到master1查询
System.out.println("=======没有配置规则的表=======");
System.out.println(userService.getById(1));
System.out.println(userService.getById(1));
}
/**
* join 连查询报错 select * from TestUser t join orders o on t.id=o.user_id
* 不支持 因为是两个数据源 联表查询报错,表不存在
*
* TestUser 垂直分片 路由到了master1的W1数据库 导致Table 'w1.orders' doesn't exist
*
*/
@Test
void selectJoin() {
//查询Testuser 路由到master1
testuserService.selectJoin();
}
/**
* 事务插入三个表的数据, 因为分库 事务失效
*/
//@Transactional
@Test
void add() {
//查询Testuser 路由到master1
Testuser testuser =new Testuser();
testuser.setName("分片4");
testuser.setAddress("北京2");
testuserService.save(testuser);
System.out.println("========");
Order order =new Order();
order.setUserId(testuser.getId());
order.setAmount(new BigDecimal("88.11"));
//验证长度过长 分布式事务可以回滚 好神奇
order.setOrderNo("分片4");
orderService.save(order);
//插入其他的表
User user =new User();
user.setAddress("分片411111111111111111111111");
userService.save(user);
System.out.println("1111");
}
}
总结:垂直分表将一个大表按照垂直切分分成两个小表,
1:不能是使用join连接查询
2:事务注解可以生效
3:查询其他表,根据配置默认查询master1
3.4:水平单表不分库分片
业务场景在现:比如一个大表orders表,水平按行拆分。按照用户id拆分,分到orders0、orders1表中,但是不分库。
1:配置application.properties,通过切换yml实现不同的功能
#应用名字和端口
spring.application.name=shardingJdbc_demo1
#开发环境
spring.profiles.active=dev
# 运行模式类型。可选配置:Memory、Standalone、Cluster
spring.shardingsphere.mode.type=Memory
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定YAML配置文件 sharding_水平多表分库分片可以进行切换
spring.datasource.url=jdbc:shardingsphere:classpath:sharding_水平单表不分库分片.yaml
2:配置sharding_水平单表不分库分片.yaml
#水平分片,将一个表按照一定的规则user_id 拆分成多个表 放到同一个数据库中
# * R_Order1 (数据库1)
# * orders0 (表0)
# * orders1 (表1)
# *
dataSources:
Order_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/R_Order1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
rules:
- !SHARDING
tables:
# 逻辑表的名称
orders:
#Order_$->{0..1}.orders->{0..1}
#分库策略 数据库的表名 Order_0.orders0,Order_0.orders1
actualDataNodes: Order_0.orders${0..1}
tableStrategy:
#分片策略 根据user_id 不能主键自增
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
## #分片算法
shardingAlgorithms:
userid_inline:
#分片算法类型
type: INLINE
#user_id % 2 放到那个数据表中 r_o$->{user_id % 2} orders_$->{user_id % 2}
props:
algorithm-expression: orders$->{user_id % 2}
#日志开启
props:
sql-show: true
3:实体entity(在这1个数据库中创建orders0、orders1表)
@TableName(value = "orders")
@Data
public class Order implements Serializable {
/**
* 主键
* //默认主键自增
* //type =IdType.AUTO 主键必须设置自增 获取插入的主键值
* //type =IdType.NONE 默认值 获取不到主键
* //type =IdType.ASSIGN_UUID uuid不能是Int类型
* //IdType.ASSIGN_ID 雪花算法设置生成id 主键不能为递增
*/
// @TableId(type = IdType.AUTO)
// private int id;
//水平分片 雪花算法
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
//使用 keyGenerateStrategy 需要设置ONE
@TableId(type = IdType.NONE)
private Long id;//不能主键自增
private Integer userId;//根据userid分库
private String orderNo;//根据order分表
private BigDecimal amount;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
4:测试类
pringBootTest
class 水平单表不分库分片3Test {
//将一个orders表拆成两个表 在同一数据库中Order_0中
@Autowired
OrdersMapper orderMapper;
/**
* 将一个大表 根据user_ID分片
* 根据 user_id % 2 轮训插入一个数据库的 orders0和orders1表
*
* INSERT INTO orders ( id, user_id, order_no, amount ) VALUES ( ?, ?, ?, ? )
*
* INSERT INTO orders1 ( id, user_id, order_no, amount ) VALUES (?, ?, ?, ?)
* INSERT INTO orders0 ( id, user_id, order_no, amount ) VALUES (?, ?, ?, ?)
*
*/
//@Transactional
@Test
void addOrder() {
Orders order = new Orders();
order.setUserId(21);//这里填写 不同的user_id来测试
order.setOrderNo(2025);
order.setAmount(new BigDecimal(100.11));
orderMapper.insert(order);
Orders order1 = new Orders();
order1.setUserId(22);//这里填写 不同的user_id来测试
order1.setOrderNo(2025);
order1.setAmount(new BigDecimal(100.11));
orderMapper.insert(order1);
System.out.println(order);
System.out.println(order1);
}
@Test
void addOrder1() {
for (int i = 0; i < 10; i++) {
Orders order = new Orders();
order.setUserId(i);//这里填写 不同的user_id来测试
order.setOrderNo(2025);
order.setAmount(new BigDecimal(100.11));
orderMapper.insert(order);
System.out.println(order);
}
}
/**
* 根据id查询 一个数据库的两个表UNION ALL 查询
* Logic SQL: SELECT id,user_id,order_no,amount FROM orders WHERE id=?
* Actual SQL:
* SELECT id,user_id,order_no,amount FROM orders0 WHERE id=?
* UNION ALL
* SELECT id,user_id,order_no,amount FROM orders1 WHERE id=?
*
* 查询全部 一个数据库的orders0 和 orders1联合查询
* Logic SQL: SELECT id,user_id,order_no,amount FROM orders
*
* Actual SQL: Order_0 :::
* SELECT id,user_id,order_no,amount FROM orders0
* UNION ALL
* SELECT id,user_id,order_no,amount FROM orders1
*
*/
@Test
void selectOrder() {
//查询 汇总根据user_id匹配
Orders order = orderMapper.selectById(13);
System.out.println(order);
System.out.println("============");
//查询两个表 UNION ALL 连接
List<Orders> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
/**
* 2025-04-28T20:26:47.872+08:00 INFO 4031 --- [SpringBoot_2_ShardingJDBC1] [ main] ShardingSphere-SQL : Logic SQL: SELECT COUNT(*) AS total FROM orders
* 2025-04-28T20:26:47.873+08:00 INFO 4031 --- [SpringBoot_2_ShardingJDBC1] [ main] ShardingSphere-SQL : Actual SQL: Order_0 ::: SELECT COUNT(*) AS total FROM orders0 UNION ALL SELECT COUNT(*) AS total FROM orders1
* 2025-04-28T20:26:48.043+08:00 INFO 4031 --- [SpringBoot_2_ShardingJDBC1] [ main] ShardingSphere-SQL : Logic SQL: SELECT id,user_id,order_no,amount FROM orders ORDER BY user_id ASC LIMIT ?,?
* 2025-04-28T20:26:48.043+08:00 INFO 4031 --- [SpringBoot_2_ShardingJDBC1] [ main] ShardingSphere-SQL : Actual SQL: Order_0 ::: SELECT id,user_id,order_no,amount FROM orders0 ORDER BY user_id ASC LIMIT ?,? ::: [0, 15]
* 2025-04-28T20:26:48.043+08:00 INFO 4031 --- [SpringBoot_2_ShardingJDBC1] [ main] ShardingSphere-SQL : Actual SQL: Order_0 ::: SELECT id,user_id,order_no,amount FROM orders1 ORDER BY user_id ASC LIMIT ?,? ::: [0, 15]
*
* 排序表面上看会查询全部数据到内存,在进行内存排序,其实不是的 是使用了归并排序,不会占用内存,效率还是比较高的
*/
@Test
void selectOrderPage() {
//分页查询 汇总根据user_id匹配
QueryWrapper<Orders> queryWrapper=new QueryWrapper<Orders>();
queryWrapper.orderByAsc("user_id");
Page<Orders> orders = orderMapper.selectPage(new Page<>(3,5),queryWrapper);
System.out.println("总页数:"+orders.getPages());
System.out.println("当前页数:"+orders.getCurrent());
List<Orders> records = orders.getRecords();
for (Orders record : records) {
System.out.println(record);
}
}
}
总结: 水平单表根据分片键路由到不同的表,查询总数据会进行合并,分页查询会进行烩归并排序,不会大量占用内存。
3.5:水平单表分库分片
业务场景在现:将一个用户表根据user_id水平拆分、分到不同的数据库,那么用户表关联的订单表再行拆分,分到指定的库中的不同的表
1:配置application.properties,通过切换yml实现不同的功能
# 运行模式类型。可选配置:Memory、Standalone、Cluster
spring.shardingsphere.mode.type=Memory
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定YAML配置文件 sharding_水平多表分库分片可以进行切换
spring.datasource.url=jdbc:shardingsphere:classpath:sharding_水平单表分库分片.yaml
2:配置sharding_水平单表分库分片.yaml
#水平分片,将一个表分到两个库中 在分别进行两个分片
# * R_Order1 (数据库1)
# * orders0 (表0)
# * orders1 (表1)
# *
# * R_Order2 (数据库2)
# * orders0 (表0)
# * orders1 (表1)
dataSources:
# 主库写
master1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
s_order0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/R_Order1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
s_order1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/R_Order2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
rules:
- !SHARDING
tables:
#逻辑表的名称
TestUser:
#绑定数据库的表名
actualDataNodes: master1.TestUser
#逻辑表的名称
orders:
#配置节点名字 Order_0.orders0,Order_0.orders1,Order_1.orders0,Order_1.orders1
actualDataNodes: s_order${0..1}.orders${0..1}
#重点1:配置分库策略 根据user_id 分库
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
#重点2:配置分表策略 根据order_no分表
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: order_no_inline
#雪花算法
keyGenerateStrategy:
column: id
keyGeneratorName: snowflake1
#2重点:分库,根据user_id 插入不同的数据库
# defaultDatabaseStrategy:
# standard:
# shardingColumn: user_id
# shardingAlgorithmName: userid_inline
#分库分表算法
shardingAlgorithms:
#分库策略 user_id决定库
userid_inline:
type: INLINE
props:
algorithm-expression: s_order${user_id % 2}
#分表策略 order_no决定表
order_no_inline:
type: HASH_MOD
props:
sharding-count: 2 # 表示有2个分片库数量
#雪花算法
keyGenerators:
snowflake1:
type: SNOWFLAKE
# props:
# worker-id: 1
#日志开启
props:
sql-show: true
3:实体entity(在这2个数据库中创建orders0、orders1表)
/**
* @TableName order
*/
@TableName(value = "orders")
@Data
public class Order implements Serializable {
/**
* 主键
* //默认主键自增
* //type =IdType.AUTO 主键必须设置自增 获取插入的主键值
* //type =IdType.NONE 默认值 获取不到主键
* //type =IdType.ASSIGN_UUID uuid不能是Int类型
* //IdType.ASSIGN_ID 雪花算法设置生成id 主键不能为递增
*/
// @TableId(type = IdType.AUTO)
// private int id;
//水平分片 雪花算法
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
//使用 keyGenerateStrategy 需要设置ONE
@TableId(type = IdType.NONE)
private Long id;//不能主键自增
private Integer userId;//根据userid分库
private String orderNo;//根据order分表
private BigDecimal amount;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
4:测试类
@SpringBootTest
class 水平单表分库分片4Test {
@Autowired
OrdersMapper orderMapper;
/**
* 水平分片 不能使用逐渐自增
* 多表的主键会重复
* 两个数据库
*
* R_Order1 (数据库1)
* orders0 (表0)
* orders1 (表1)
*
* R_Order2 (数据库2)
* orders0 (表0)
* orders1 (表1)
*
*/
@Test
void addOrder() {
for (int i = 10; i < 15; i++) {
Orders order=new Orders();
order.setUserId(1);//根据user_id分库 userID是user表生成的id,不知自己的
order.setOrderNo(i);//根据order_no分表
order.setAmount(new BigDecimal(200));
orderMapper.insert(order);
}
for (int i = 15; i <20; i++) {
Orders order=new Orders();
order.setUserId(2);//根据user_id分库 userID是user表生成的id,不知自己的
order.setOrderNo(i);//根据order_no分表
order.setAmount(new BigDecimal(200));
orderMapper.insert(order);
}
}
/**
* 两个sql 查询拼接 分别查询两个库的两个表 数据汇集
* Logic SQL: SELECT id,user_id,order_no,amount FROM orders
* Actual SQL: s_order0 ::: SELECT id,user_id,order_no,amount FROM orders0 UNION ALL SELECT id,user_id,order_no,amount FROM orders1
* Actual SQL: s_order1 ::: SELECT id,user_id,order_no,amount FROM orders0 UNION ALL SELECT id,user_id,order_no,amount FROM orders1
*/
@Test
void selectOrder() {
List<Orders> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
//查询的是全部数据,进行结果合并,然后是归并排序
//https://shardingsphere.apache.org/document/4.0.0-RC2/cn/features/sharding/use-norms/pagination/
//
@Test
void 分页() {
//current 当前页 页面显示条数
IPage<Orders> page = new Page<>(3, 2);
List<Orders> orders = orderMapper.selectList(page,null);
orders.forEach(System.out::println);
}
/**
* 根据amount查询(查询方式)
* Actual SQL: s_order0 :::
* Logic SQL: SELECT id,user_id,order_no,amount FROM orders WHERE (amount = ?)
*
* Actual SQL: s_order0 :::
*
* SELECT id,user_id,order_no,amount FROM orders0
* WHERE (amount = ?)
* UNION ALL SELECT id,user_id,order_no,amount FROM orders1
* WHERE (amount = ?)
*
* Actual SQL: s_order1 :::
* SELECT id,user_id,order_no,amount FROM orders0
* WHERE (amount = ?)
* UNION ALL
* SELECT id,user_id,order_no,amount FROM orders1
* WHERE (amount = ?) ::: [200, 200]
*/
@Test
void select1() {
Map<String,Object> map=new HashMap<>();
map.put("amount",200);//
//map.put("user_id",1);
List<Orders> list=orderMapper.selectByMap(map);
list.forEach(System.out::println);
System.out.println("=========");
//根据user_id分库 路由到指定的数据库 查询两个表的数据
//Actual SQL: s_order0 ::: SELECT id,user_id,order_no,amount FROM orders0
// WHERE (user_id = ?) UNION ALL SELECT id,user_id,order_no,amount FROM orders1
// WHERE (user_id = ?)
//
QueryWrapper<Orders> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("user_id",2);
List<Orders> orders = orderMapper.selectList(queryWrapper);
orders.forEach(System.out::println);
}
/**
* 根据order_no 查询
* 会查询两个数据库
*
*/
@Test
void select2() {
QueryWrapper<Orders> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("order_no",8);
// queryWrapper.or();
// queryWrapper.eq("order_no",2);
List<Orders> orders = orderMapper.selectList(queryWrapper);
orders.forEach(System.out::println);
}
/**
* 主键雪花算法
*/
@Test
void addOrder1() {
for (int i = 100; i < 110; i++) {
Orders order=new Orders();
order.setUserId(i);
order.setOrderNo(1);
order.setAmount(new BigDecimal(120));
orderMapper.insert(order);
}
}
}
3.6:水平多表分库分表
业务场景在现:将一个订单表、订单详情表。根据user_id水平拆分、分到不同的数据库的不同表中 这里有两个数据库4张表
1:配置application.properties,通过切换yml实现不同的功能
# 运行模式类型。可选配置:Memory、Standalone、Cluster
spring.shardingsphere.mode.type=Memory
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定YAML配置文件 sharding_水平多表分库分片可以进行切换
spring.datasource.url=jdbc:shardingsphere:classpath:sharding_水平多表分库分片.yaml
2:配置sharding_水平多表分库分片.yaml
#水平分片,将一个表分到两个库中 在分别进行两个分片
# * R_Order1 (数据库1 订单表和订单详情表)
# * orders0 (表0) order_item0
# * orders1 (表1) order_item1
# *
# * R_Order2 (数据库2)
# * orders0 (表0) order_item0
# * orders1 (表1) order_item1
dataSources:
s_order0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/R_Order1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
s_order1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/R_Order2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 主库写
master1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
rules:
- !SHARDING
tables:
#逻辑表的名称 实际表名TestUser 只会插入master1
TestUser:
#绑定数据库的表名
actualDataNodes: master1.TestUser
# #逻辑表的名称 实际表名dict
# dict:
# #绑定数据库的节点
# actualDataNodes: master1.dict,s_order${0..1}.dict
#逻辑表orders的名称 实际表名orders0 orders1
orders:
#配置节点名字 Order_0.orders0,Order_0.orders1,Order_1.orders0,Order_1.orders1
actualDataNodes: s_order${0..1}.orders${0..1}
#重点1:配置分库策略 根据user_id 分库
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
#重点2:配置分表策略 根据order_no分表
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: order_no_inline
#雪花算法
keyGenerateStrategy:
column: id
keyGeneratorName: snowflake1
order_item:
#配置节点名字 Order_0.orders0,Order_0.orders1,Order_1.orders0,Order_1.orders1
actualDataNodes: s_order${0..1}.order_item${0..1}
#重点1:配置分库策略 根据user_id 分库
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
#重点2:配置分表策略 根据order_no分表
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: order_no_inline
#雪花算法
keyGenerateStrategy:
column: id
keyGeneratorName: snowflake1
#这两个表的分表规则一致 必须需使用分片键进行关联 绑定表减少笛卡尔积 会少查询
bindingTables:
- orders,order_item
#广播表
broadcastTables:
- dict
#分库分表算法
shardingAlgorithms:
#分库策略 user_id决定库
userid_inline:
type: INLINE
props:
algorithm-expression: s_order${user_id % 2}
#分表策略 order_no决定表
order_no_inline:
type: HASH_MOD
props:
sharding-count: 2 # 表示有2个分片库数量
#雪花算法
keyGenerators:
snowflake1:
type: SNOWFLAKE
#日志开启
props:
sql-show: true
3:实体entity(在这2个数据库中创建orders0、orders1、order_item0、order_item1)
/**
* @TableName order
*/
@TableName(value = "orders")
@Data
public class Order implements Serializable {
/**
* 主键
* //默认主键自增
* //type =IdType.AUTO 主键必须设置自增 获取插入的主键值
* //type =IdType.NONE 默认值 获取不到主键
* //type =IdType.ASSIGN_UUID uuid不能是Int类型
* //IdType.ASSIGN_ID 雪花算法设置生成id 主键不能为递增
*/
// @TableId(type = IdType.AUTO)
// private int id;
//水平分片 雪花算法
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
//使用 keyGenerateStrategy 需要设置ONE
@TableId(type = IdType.NONE)
private Long id;//不能主键自增
private Integer userId;//根据userid分库
private String orderNo;//根据order分表
private BigDecimal amount;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
@TableName(value ="order_item")
@Data
public class OrderItem implements Serializable {
/**
* type=IdType.NONE 让jdbc的分布式序列生成主键
*/
@TableId(type=IdType.AUTO)
private Long id;
private String orderNo;//根据orderNo分表
private Long userId;
private BigDecimal price;//价格
private Integer count;//数量
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
/**
* 字典表 无论哪个数据都有数据
* @TableName dict
*/
@TableName(value ="dict")
@Data
public class Dict implements Serializable {
@TableId
private Long id;
private String dictType;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
4:测试类
@SpringBootTest
class 水平多表分库分片Test {
@Autowired
TestuserMapper testuserMapper;
@Autowired
OrderMapper orderMapper;
@Autowired
DictMapper dictMapper;
/**
* #水平分片,将一个表分到两个库中 在分别进行两个分片
* # * R_Order1 (数据库1 订单表和订单详情表)
* # * orders0 (表0) order_item0
* # * orders1 (表1) order_item1
* # *
* # * R_Order2 (数据库2)
* # * orders0 (表0) order_item0
* # * orders1 (表1) order_item1
* <p>
* 指定YAML配置文件
* spring.datasource.url=jdbc:shardingsphere:classpath:sharding_水平多表分库分片.yaml
*/
//测试关联表插入数据
@Test
void addOrderAndItem() {
//插入数据库1 user_id=1
for (int i = 0; i < 5; i++) {
Order order = new Order();
order.setUserId(1);//进入1库
order.setOrderNo("HU" + i);//不同表
order.setAmount(new BigDecimal(666));
orderMapper.insert(order);
for (int j = 0; j < 3; j++) {
OrderItem orderItem=new OrderItem();
orderItem.setUserId(1L);
orderItem.setOrderNo("HU"+i);
orderItem.setCount(2);
orderItem.setPrice(new BigDecimal(300.33));
orderItemMapper.insert(orderItem);
}
}
//插入数据库0 user_id=1
for (int i = 5; i < 10; i++) {
Order order = new Order();
order.setUserId(2);//进入0库
order.setOrderNo("FU" + i);
//order.setAmount(new BigDecimal(888));
orderMapper.insert(order);
for (int j = 0; j < 3; j++) {
OrderItem orderItem=new OrderItem();
orderItem.setUserId(2L);
orderItem.setOrderNo("FU"+i);
orderItem.setCount(2);
orderItem.setPrice(new BigDecimal(400.44));
orderItemMapper.insert(orderItem);
}
}
}
@Autowired
OrderItemMapper orderItemMapper;
//测试关联表插入数据
@Test
void addOrderItem() {
//插入数据库1 user_id=1
for (int i = 0; i < 5; i++) {
OrderItem orderItem = new OrderItem();
orderItem.setUserId(1L);
orderItem.setOrderNo("HU" + i);
orderItem.setCount(2);
orderItem.setPrice(new BigDecimal(300.33));
orderItemMapper.insert(orderItem);
}
}
/**
* 关联表查询 需要配置关联关系
*
* bindingTables:
* - orders,order_item
*
*
* select o.order_no,SUM(i.count*i.price) as amount
* from orders0 o LEFT JOIN order_item0 i
* on o.order_no=i.order_no
* GROUP BY o.order_no
*
* 会出现笛卡尔集 order0*order_item1h 和 order1*order_item0不会出现这种情况
* order0*order_item0 order0*order_item1
* order1*order_item0 order1*order_item1
*/
@Test
public void select关联表(){
List<OrderVo> aa = orderMapper.selectOrderAmount(11);
aa.forEach(System.out::println);
}
//三个库都创建字典表 add的时候会插入三个数据库
@Test
public void 字典表插入(){
Dict dict=new Dict();
dict.setDictType("字典表5");
int insert = dictMapper.insert(dict);
System.out.println(dict);
List<Dict> dicts = dictMapper.selectList(null);
dicts.forEach(System.out::println);
}
//三个库随机查询
@Test
public void 字典表查询(){
List<Dict> dicts = dictMapper.selectList(null);
dicts.forEach(System.out::println);
}
}
4:ShardingProxy实现代码案例
ShardingProxy好处就是可以单机可以集群。当我们的项目有很多的时候,不需要修改每一个机器上的yml。ShardingProxy会统一管理yml,只需要修改一份就可以了
4.1:下载解压配置ShardingProxy
使用二进制发布包启动 Proxy,需要环境具备 Java JRE 8 或更高版本。
下载解压之后:
conf目录下边有很多配置

4.2:配置conf下边的server.yaml
# 配置proxy的用户名和密码 两个用户root和sharding
authority:
users:
- user: root@%
password: root
- user: sharding
password: sharding
privilege:
type: ALL_PERMITTED
#
#transaction:
# defaultType: XA
# providerType: Atomikos
#
#sqlParser:
# sqlCommentParseEnabled: false
# sqlStatementCache:
# initialCapacity: 2000
# maximumSize: 65535
# parseTreeCache:
# initialCapacity: 128
# maximumSize: 1024
#
#logging:
# loggers:
# - loggerName: ShardingSphere-SQL
# additivity: true
# level: INFO
# props:
# enable: false
#
props:
# system-log-level: INFO
# max-connections-size-per-query: 1
# kernel-executor-size: 16 # Infinite by default.
# proxy-frontend-flush-threshold: 128 # The default value is 128.
# proxy-hint-enabled: false
# # sql-show is the same as props in logger ShardingSphere-SQL, and its priority is lower than logging rule
sql-show: true
# check-table-metadata-enabled: false
# # Proxy backend query fetch size. A larger value may increase the memory usage of ShardingSphere Proxy.
# # The default value is -1, which means set the minimum value for different JDBC drivers.
# proxy-backend-query-fetch-size: -1
# proxy-frontend-executor-size: 0 # Proxy frontend executor size. The default value is 0, which means let Netty decide.
# # Available options of proxy backend executor suitable: OLAP(default), OLTP. The OLTP option may reduce time cost of writing packets to client, but it may increase the latency of SQL execution
# # and block other clients if client connections are more than `proxy-frontend-executor-size`, especially executing slow SQL.
# proxy-backend-executor-suitable: OLAP
# proxy-frontend-max-connections: 0 # Less than or equal to 0 means no limitation.
# # Available sql federation type: NONE (default), ORIGINAL, ADVANCED
# sql-federation-type: NONE
proxy-mysql-default-version: 8.0.11 # 这里使用8.0.11
# proxy-default-port: 3307 # Proxy default port.
# proxy-netty-backlog: 1024 # Proxy netty backlog.
# cdc-server-port: 33071 # CDC server port
4.3:配置垂直拆分conf下的config-sharding.yaml
这里的配置和ShardingJDBC的垂直拆分配置一样,在这里配置好了,就不需要每个项目都配置了
######################################################################################################
#垂直分片设置
#垂直分片,一个表属性太多,进行垂直分片
#将一个大表 垂直拆分到两个数据库W1(TestUser) W11(Orders)
#数据库名字 springboot的配置需要这里的数据库名字
databaseName: sharding_db
dataSources:
master1:
url: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
master2:
url: jdbc:mysql://localhost:3306/W11?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
#表名
TestUser:
#绑定数据库的表名
actualDataNodes: master1.TestUser
#表名
orders:
#绑定数据库的表名
actualDataNodes: master2.orders
4.4:Proxy代码测试
1:导入pom
<!--MybatisPlus的jar 3.0基于jdk8-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
2:配置 application.properties 数据源是Proxy代理的是数据源
#ShardingProxy 测试直接配置数据源即可
#注意:数据源是Proxy的数据源 账户、密码、数据库都在proxy的配置文件中
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#readwrite_splitting_db(读写分离数据库)
#sharding_db (垂直分片数据库)
spring.datasource.url=jdbc:mysql://127.0.0.1:3307/sharding_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC
spring.datasource.username=sharding
spring.datasource.password=sharding
3:代码测试
@SpringBootTest
class ShardingProxy垂直分片Test {
@Autowired
TestuserMapper testuserMapper;
@Autowired
OrderMapper orderMapper;
/**
* 将一个大表 垂直拆分到两个数据库W1(TestUser) W11(Orders)
* 插入数据 分别插入两个数据库
*/
@Transactional
@Test
void addUser_Order() {
Testuser testuser=new Testuser();
testuser.setName("proxy6");
testuser.setAddress("proxy6");
testuserMapper.insert(testuser);//添加用户信息
System.out.println(testuser);
Order order=new Order();
order.setUserId(testuser.getId());
order.setOrderNo("2222");
order.setAmount(new BigDecimal(500));
orderMapper.insert(order);
}
/**
* 将一个大表 垂直拆分到两个数据库W1(TestUser) W11(Orders)
*/
//@Transactional
@Test
void selectUser_Order() {
Testuser testuser = testuserMapper.selectById(52);
System.out.println(testuser);
List<Order> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
}

1万+

被折叠的 条评论
为什么被折叠?



