单库单表
假设现在有一个存储商品信息的数据库db_product,db_product里面有一张Product商品信息表,里面存储了商品ID和商品名称,先
使用Springboot搭建一个项目,Mysql作为数据库,对Product表进行操作。
初始化数据库
创建数据库db_product,和product商品表:
CREATE DATABASE IF NOT EXISTS db_product DEFAULT CHARACTER SET utf8;
USE db_product;
CREATE TABLE product(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建SpringBoot项目
Pom依赖
<?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.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shardingshpere</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<!--单元测试相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
数据源配置:
## 数据源配置(单库单表)
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/db_product?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = root
Product
/**
* 商品信息
*/
public class Product {
/**
* id
*/
private long productId;
/**
* name
*/
private String productName;
// 省略了get set
}
Mapper文件
@Mapper
public interface ProductMapper {
/**
* 新增
* @param product
*/
void insert(Product product);
/**
* 根据id查找
* @param productId
* @return
*/
Product selectById(@Param("productId") long productId);
}
<?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.shardingsphere.demo.mapper.ProductMapper">
<resultMap id="product" type="com.shardingsphere.demo.entity.Product">
<result column="product_id" jdbcType="INTEGER" property="productId" />
<result column="product_name" jdbcType="VARCHAR" property="productName" />
</resultMap>
<select id="selectById" resultMap="product">
select * from product where product_id = #{productId}
</select>
<insert id="insert">
INSERT INTO product(product_id, product_name) values (#{productId}, #{productName});
</insert>
</mapper>
整个项目的结构:
单元测试
/**
* 单库单表单元测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductSingleTest {
@Autowired
ProductMapper productMapper;
@Test
public void insertTest() {
// 新增数据
Product product = new Product();
product.setProductName("手机");
product.setProductId(1);
productMapper.insert(product);
}
}
查询数据库:
mysql> select * from product;
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 1 | 手机 |
+------------+--------------+
1 row in set (0.00 sec)
分库分表
接下来我们要对项目进行改造,对数据进行分库分表。
Pom文件引入shardingsphere依赖:
<!--shardingsphere-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
水平分表
首先,对Product进行水平分表,先拆分成两个表product_0和product_1。
拆分规则:
根据product_id进行水平拆分,product_id对2进行取模,结果为0的时候进入product_0表,结果为1的时候进入product_1。
- 数据库修改
在db_product下创建product_0和product_1数据表。
USE db_product;
CREATE TABLE product_0(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE product_1(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 配置文件修改,引入shardingsphere
- 配置shardingsphere的数据源
- 指定表的数据源和表名称的规则
- 指定主键和主键生成规则
- 指定分表的规则
# 配置数据源,当前只有一个ds1
spring.shardingsphere.datasource.names=ds1
# 数据源ds1,数据库db_product
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://localhost:3306/db_product?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
# 指定表的数据源和表名称的规则,这里指定product表的数据源是ds1,表名称是product_0、product_1
spring.shardingsphere.sharding.tables.product.actual-data-nodes=ds1.product_$->{0..1}
# 指定主键,product的主键为product_id
spring.shardingsphere.sharding.tables.product.key-generator.column=product_id
# 指定用来执行分片规则的字段,这里使用product_id来作为分片的字段
spring.shardingsphere.sharding.tables.product.table-strategy.inline.sharding-column=product_id
# 主键生成规则,使用SNOWFLAKE生成主键
spring.shardingsphere.sharding.tables.product.key-generator.type=SNOWFLAKE
# 指定分表规则,这里根据product_id对2进行取模来决定数据落到哪个表,对2取模为0的话落到_0结尾的表,取模结果为1落到_1的表
spring.shardingsphere.sharding.tables.product.table-strategy.inline.algorithm-expression=product_$->{product_id % 2}
spring.main.allow-bean-definition-overriding=true
key-generator.type用来设置主键的生成规则,这里使用了SNOWFLAKE算法进行生成,但是单元测试进行数据添加的时候如果不设置主键,并没有使用SNOWFLAKE生成,待研究。
spring.shardingsphere.sharding.tables.product.key-generator.type=SNOWFLAKE
- 编写单元测试
/**
* 水平拆分单元测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductHroizontalTest {
@Autowired
ProductMapper productMapper;
@Test
public void insertTest() {
// 新增数据
Product product = new Product();
product.setProductName("手机");
product.setProductId(1);// 1对2取模为1,应该落到product_1表
Product product1 = new Product();
product1.setProductName("电脑");
product1.setProductId(2);// 2对2取模为0,应该落到product_0表
productMapper.insert(product);
productMapper.insert(product1);
}
@Test
public void selectTest(){
Product product = productMapper.selectById(1);
System.out.println(product.getProductName());
}
}
- 查询数据库进行验证:
mysql> select * from product_1;
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 1 | 手机 |
+------------+--------------+
1 row in set (0.00 sec)
mysql> select * from product_0;
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 2 | 电脑 |
+------------+--------------+
1 row in set (0.00 sec)
可以看到id为1的落到了product_1表,id为2的落到了product_0表。
水平分库
水平分表已经完成,接下来我们尝试水平分库,先改造数据库,创建两个商品信息库,分别为db_product_0和db_product_1,每个库中的表相同,都有两张表product_0和product_1,水平分表的规则不变,依旧是使用product_id对2进行取模,水平分库的规则也采用这种方式,根据取模结果来判断落到db_product_0还是db_product_1。
- 数据库创建
# 创建数据库db_product_0
CREATE DATABASE IF NOT EXISTS db_product_0 DEFAULT CHARACTER SET utf8;
USE db_product_0;
CREATE TABLE product_0(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE product_1(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 创建数据库db_product_1
CREATE DATABASE IF NOT EXISTS db_product_1 DEFAULT CHARACTER SET utf8;
USE db_product_1;
CREATE TABLE product_0(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE product_1(
product_id int,
product_name VARCHAR(900) NOT NULL,
PRIMARY KEY (product_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 配置文件修改
- 由于我们增加了数据库,所以需要在配置文件中再增加一个数据源ds1
- actual-data-nodes需要将product表指向两个数据源,所以修改为ds$->{0…1}
- 增加database-strategy配置,配置分库策略
# 所有的数据源,当前有两个数据源
spring.shardingsphere.datasource.names=ds0,ds1
# 数据源ds1,连接db_product_1
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://localhost:3306/db_product_1?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
# 数据源ds0,连接db_product_0
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://localhost:3306/db_product_0?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
# 指定表的数据源和表名称的规则,这里指定product表的数据源是ds0和ds1,product表名称是以_0、_1结尾的:product_0、product_1
spring.shardingsphere.sharding.tables.product.actual-data-nodes=ds$->{0..1}.product_$->{0..1}
# 主键配置
# 指定主键,product的主键为product_id
spring.shardingsphere.sharding.tables.product.key-generator.column=product_id
# 主键生成规则,使用SNOWFLAKE生成主键
spring.shardingsphere.sharding.tables.product.key-generator.type=SNOWFLAKE
# 分表规则配置
# 指定用来执行分片的字段,这里设置product的product_id来作为分片的字段
spring.shardingsphere.sharding.tables.product.table-strategy.inline.sharding-column=product_id
# 指定分片规则,这里根据product_id对2进行取模来决定数据落到哪个表,对2取模为0的话落到_0结尾的表,取模结果为1落到_1的表
spring.shardingsphere.sharding.tables.product.table-strategy.inline.algorithm-expression=product_$->{product_id % 2}
# 分库规则配置
# 指定用来执行分库规则的字段,这里同样设置product的product_id来作为分库的字段
spring.shardingsphere.sharding.tables.product.database-strategy.inline.sharding-column=product_id
# 指定分库规则,这里根据product_id对2进行取模来决定数据落到哪个库,对2取模为0的话落到ds0数据源,取模结果为1落到ds1数据源
spring.shardingsphere.sharding.tables.product.database-strategy.inline.algorithm-expression=ds$->{product_id % 2}
spring.main.allow-bean-definition-overriding=true
- 编写单元测试
@Test
public void insertTest() {
// 新增数据
Product product = new Product();
product.setProductName("手机");
product.setProductId(1);// 应该落到db_product_1的product_1表
Product product1 = new Product();
product1.setProductName("电脑");
product1.setProductId(2);// 应该落到db_product_0的product_0表
productMapper.insert(product);
productMapper.insert(product1);
}
- 查询数据库进行验证:
mysql> use db_product_1;
Database changed
mysql> select * from product_1;
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 1 | 手机 |
+------------+--------------+
1 row in set (0.00 sec)
mysql> use db_product_0;
Database changed
mysql> select * from product_0;
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 2 | 电脑 |
+------------+--------------+
1 row in set (0.00 sec)
可以看到id为1的数据落到了db_product_1下的product_1表,id为2的落到了db_product_0下的product_0表。
垂直分库
我们已经对商品完成了水平分库分表,假设现在有了商品的订单信息,订单信息想要存到订单库中,不与商品使用一个库,接下来看一下如何实现。
- 数据库改造,增加订单库db_order和订单表,订单库先不拆分,只对订单表进行水平拆分,所以增加order_0和order_1表,水平分表规则同样使用id对2进行取模。
CREATE DATABASE IF NOT EXISTS db_order DEFAULT CHARACTER SET utf8;
USE db_order;
CREATE TABLE order_0(
order_id int,
order_info VARCHAR(900) NOT NULL,
PRIMARY KEY (order_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE order_1(
order_id int,
order_info VARCHAR(900) NOT NULL,
PRIMARY KEY (order_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
增加Order相关文件
/** * 订单信息 */ public class Order { /** * id */ private long orderId; /** * 信息 */ private String orderInfo; public long getOrderId() { return orderId; } public void setOrderId(long orderId) { this.orderId = orderId; } public String getOrderInfo() { return orderInfo; } public void setOrderInfo(String orderInfo) { this.orderInfo = orderInfo; } }
Mapper
@Mapper public interface OrderMapper { /** * 新增 * @param order */ void insert(Order order); }
<?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.shardingsphere.demo.mapper.OrderMapper"> <resultMap id="order" type="com.shardingsphere.demo.entity.Order"> <result column="order_id" jdbcType="INTEGER" property="orderId" /> <result column="order_info" jdbcType="VARCHAR" property="orderInfo" /> </resultMap> <insert id="insert"> INSERT INTO order(order_id, order_info) values (#{orderId}, #{orderInfo}); </insert> </mapper>
-
修改配置文件
- 增加订单库的数据源
- 增加订单库的水平分表配置
- 增加订单库的水平分库配置(这里订单先不进行水平分库)
# 所有的数据源
spring.shardingsphere.datasource.names=ds0,ds1,order0
# 数据源ds1,连接db_product_1
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://localhost:3306/db_product_1?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
# 数据源ds0,连接db_product_0
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://localhost:3306/db_product_0?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
# 数据源order0,连接db_order
spring.shardingsphere.datasource.order0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.order0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.order0.url=jdbc:mysql://localhost:3306/db_order?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.order0.username=root
spring.shardingsphere.datasource.order0.password=root
# product表的配置
# 指定表的数据源和表名称的规则,这里指定product表的数据源是ds0和ds1,product表名称是以_0、_1结尾的:product_0、product_1
spring.shardingsphere.sharding.tables.product.actual-data-nodes=ds$->{0..1}.product_$->{0..1}
# 主键配置
# 指定主键,product的主键为product_id
spring.shardingsphere.sharding.tables.product.key-generator.column=product_id
# 主键生成规则,使用SNOWFLAKE生成主键
spring.shardingsphere.sharding.tables.product.key-generator.type=SNOWFLAKE
# 分表规则配置
# 指定用来执行分片的字段,这里设置product的product_id来作为分片的字段
spring.shardingsphere.sharding.tables.product.table-strategy.inline.sharding-column=product_id
# 指定分片规则,这里根据product_id对2进行取模来决定数据落到哪个表,对2取模为0的话落到_0结尾的表,取模结果为1落到_1的表
spring.shardingsphere.sharding.tables.product.table-strategy.inline.algorithm-expression=product_$->{product_id % 2}
# 分库规则配置
# 指定用来执行分库规则的字段,这里同样设置product的product_id来作为分库的字段
spring.shardingsphere.sharding.tables.product.database-strategy.inline.sharding-column=product_id
# 指定分库规则,这里根据product_id对2进行取模来决定数据落到哪个库,对2取模为0的话落到ds0数据源,取模结果为1落到ds1数据源
spring.shardingsphere.sharding.tables.product.database-strategy.inline.algorithm-expression=ds$->{product_id % 2}
# order表的配置
# 指定表的数据源和表名称的规则,这里指定order表的数据源是order0, 同样使用order_1 order_0格式的表名
spring.shardingsphere.sharding.tables.order.actual-data-nodes=order$->{0}.order_$->{0..1}
# 主键配置
# 指定主键,order主键设置为order_id
spring.shardingsphere.sharding.tables.order.key-generator.column=order_id
# 主键生成规则,使用SNOWFLAKE生成主键
spring.shardingsphere.sharding.tables.order.key-generator.type=SNOWFLAKE
# 水平分表配置
# 指定用来执行分片规则的字段,这里设置order的order_id来作为分片的字段
spring.shardingsphere.sharding.tables.order.table-strategy.inline.sharding-column=order_id
# 指定水平分表规则,这里根据order_id对2进行取模来决定数据落到哪个表,对2取模为0的话落到_0结尾的表,取模结果为1落到_1的表
spring.shardingsphere.sharding.tables.order.table-strategy.inline.algorithm-expression=order_$->{order_id % 2}
# 水平分库的配置
# 指定用来执行分库规则的字段,这里同样设置order的order_id来作为分库的字段
spring.shardingsphere.sharding.tables.order.database-strategy.inline.sharding-column=order_id
# 指定分库规则,order表先不分库,直接指定order0数据源即可
spring.shardingsphere.sharding.tables.order.database-strategy.inline.algorithm-expression=order0
spring.main.allow-bean-definition-overriding=true
- 编写单元测试
/**
* 垂直分库单元测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderVerticalTest {
@Autowired
OrderMapper orderMapper;
@Test
public void insertTest() {
// 新增数据
Order order = new Order();
order.setOrderId(1);
order.setOrderInfo("下单了一部手机");
orderMapper.insert(order);
// 新增数据
Order order2 = new Order();
order2.setOrderId(2);
order2.setOrderInfo("下单了一台电脑");
orderMapper.insert(order2);
}
}
- 查询验证
mysql> select * from order_0;
+----------+-----------------------+
| order_id | order_info |
+----------+-----------------------+
| 2 | 下单了一台电脑 |
+----------+-----------------------+
1 row in set (0.00 sec)
mysql> select * from order_1;
+----------+-----------------------+
| order_id | order_info |
+----------+-----------------------+
| 1 | 下单了一部手机 |
+----------+-----------------------+
1 row in set (0.00 sec)
可以发现对Order的操作,数据会落到db_order下的表中。