shardingjdbc介绍
定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。它使⽤客⼾端直连数据库,以 jar 包形式提供服务,⽆需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
- 适⽤于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使⽤ JDBC。
- ⽀持任何第三⽅的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
- ⽀持任意实现 JDBC 规范的数据库,⽬前⽀持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。
——官方文档《shardingsphere_docs_cn.pdf》
一、项目说明
通过整合springboot+mybatisplus+shardingjdbc,包含订单、商品和用户三个实体的增删改查操作的分库分表demo工程
项目结构
二、准备
三个数据库,实现水平分库,三个表结构一致的user表,实现水平分表。
sql脚本,在resources/sql/ddl.txt中,按照脚本可以创建需要的数据库和表。
三、依赖
引入shardingjdbc依赖
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>3.1.0</version>
</dependency>
四、配置
shardingjdbc可以做到对代码零侵入性,在springboot整合mybatis-plus的基础上,做好分库分表配置即可。
以下重点对配置进行介绍
使用shardingjdbc进行分库分表,那么首先我们需要告诉它我们有哪几个数据源,然后按照什么规则进行分库和分表。
1. 项目配置
# 同一个主机,两个库,演示分库分表
# 数据源 db0,db1
sharding.jdbc.datasource.names=db0,db1
# 第一个数据库
sharding.jdbc.datasource.db0.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.db0.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.db0.jdbc-url=jdbc:mysql://192.168.99.105:3306/ecommerce1?useUnicode=true&characterEncoding=utf-8
sharding.jdbc.datasource.db0.username=root
sharding.jdbc.datasource.db0.password=masterpwd
# 第二个数据库
sharding.jdbc.datasource.db1.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.db1.jdbc-url=jdbc:mysql://192.168.99.105:3306/ecommerce2?useUnicode=true&characterEncoding=utf-8
sharding.jdbc.datasource.db1.username=root
sharding.jdbc.datasource.db1.password=masterpwd
# user表和item表 分库不分表
#user表
# 分库策略
sharding.jdbc.config.sharding.tables.t_user.database-strategy.inline.sharding-column=id
sharding.jdbc.config.sharding.tables.t_user.database-strategy.inline.algorithm-expression=db$->{id%2}
sharding.jdbc.config.sharding.tables.t_user.actual-data-nodes=db$->{0..1}.t_user
# 配置主键生成策略
sharding.jdbc.config.sharding.tables.t_user.key-generator.column=id
sharding.jdbc.config.sharding.tables.t_user.key-generator.type=SNOWFLAKE
#item表
# 分库策略
sharding.jdbc.config.sharding.tables.t_item.database-strategy.inline.sharding-column=id
sharding.jdbc.config.sharding.tables.t_item.database-strategy.inline.algorithm-expression=db$->{id%2}
sharding.jdbc.config.sharding.tables.t_item.actual-data-nodes=db$->{0..1}.t_item
# 配置主键生成策略
sharding.jdbc.config.sharding.tables.t_item.key-generator.column=id
sharding.jdbc.config.sharding.tables.t_item.key-generator.type=SNOWFLAKE
#order表
# 分库策略
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.algorithm-expression=db$->{user_id%2}
#分表策略
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=db$->{0..1}.t_order_$->{0..15}
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{id%16}
# 配置主键生成策略
sharding.jdbc.config.sharding.tables.t_order.key-generator.column=id
sharding.jdbc.config.sharding.tables.t_order.key-generator.type=SNOWFLAKE
# 打印执行的数据库以及语句
sharding.jdbc.config.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
# 绑定表,若不配置,两表关联查询会使用笛卡尔积的方式查询
sharding.binding-tables[0]=t_user,t_order
2. 配置说明
2.1 概念说明
- 数据节点
数据库+表构成数据所在的存储位置。 - 逻辑表名
代码中用到的表名,可以理解为各个分表的抽象。t_order - 实际表名
数据库里具体的表名,t_order_0,t_order_1....
下面这条配置中,逻辑表名是t_order,数据节点是db->{0..15}。
代码中如果使用select * from t_order进行查询,shardingjdbc会按照分片键和分片算法找到具体的表民,比如db0数据库的t_order_1表。
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=db$->{0..1}.t_order_$->{0..15}
2.2 如何配置多数据源
告诉了shardingjdbc,我们将把表分几个数据库,本工程分2个数据库。
sharding.jdbc.datasource.names 为数据库申明几个别名
可以看到在配置同步数据源时,和jdbc数据源的配置非常相似,所不同的是多了个shardingjdbc前缀,中间加了个数据库别名
2.3 如何分库分表
本工程采用inline行表达式分片算法进行分库分表,使用简单的groovy表达式,就可以告诉shardingjdbc我们按照什么规则进行分库,通俗说就是当我们要插入一条数据,shardingjdbc根据我们配置的键和规则,将这条数据插入到指定库和表中。
如下图,第一部分的分库配置,指定了采用inline分片策略,分片算法表达式是db$->{0..1}.t_order_$->{0..15}
,对t_order表的user_id进行取模,根据结果将sql路由到db0或db1。
同理,第二部分的分表配置,分表策略,指定了采用inline分片策略,分片算法表达式是t_order_$->{id%16}
,对t_order表的user_id进行取模,根据结果将sql路由到t_order_0、t_order_1...t_order_15。
你会发现上下两段配置极其相似,差别就是一个是database-strategy,table-strategy。可见shardingjdbc的设计是多么统一。
# 分库策略
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.tables.t_order.database-strategy.inline.algorithm-expression=db$->{user_id%2}
#分表策略
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=db$->{0..1}.t_order_$->{0..15}
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{id%16}
2.4 如何设置主键生成方式
配置好逻辑表名和主键名
sharding.jdbc.config.sharding.tables.t_order.key-generator.column=id
sharding.jdbc.config.sharding.tables.t_order.key-generator.type=SNOWFLAKE
2.5 分库不分表
t_item和t_user表就是分库不分表
以t_item表为例,相对于t_order分库分表,我们只需要配置分库,然后注意数据节点配置中的groovy表达式写法,因为不分表,所以每个表的表名都是一样的,即t_item。
五、演示
5.1 演示写
- 代码
自动生成的主键1370385793309491202,按照分库分表配置的分片算法(db$->{user_id%2}
,t_order_$->{id%16}
),对2取模为0,所以选择了db0库,对16取模为2,所以走了t_order_2表
@Test
public void saveOrder(){
Order order = new Order(null,"orderNo",1370295535473528834L,1370301749800476674L,12.0,1,System.currentTimeMillis(), System.currentTimeMillis());
boolean success = orderService.save(order);
System.out.println(success);
}
- 执行结果
2021-03-12 22:46:48.703 INFO 4455 --- [ main] ShardingSphere-SQL : Actual SQL: db0 ::: INSERT INTO t_order_2 ( id,
order_no,
user_id,
item_id,
order_price,
status,
create_time,
update_time ) VALUES ( ?,
?,
?,
?,
?,
?,
?,
? ) ::: [[1370385793309491202, orderNo, 1370295535473528834, 1370301749800476674, 12.0, 1, 1615560408154, 1615560408154]]
true
5.2 演示读
- 代码
@Test
public void get(){
//查询条件只有order的主键,走两个库,然后根据分片策略查t_order_2表
Order order = orderService.getById(1370304180772954114L);
System.out.println(order);
//查询条件有user_id,和id,先根据user_id选择db0,再根据id选择t_order_2表
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id",1370304180772954114L);
queryWrapper.eq("user_id",1370295535473528834L);
Order order2 = orderService.getOne(queryWrapper);
System.out.println(order2);
}
- 执行结果
t_order表是按照user_id进行分库的,当没有设置user_id条件,两个库都查,然后按照配置的分片算法(t_order_$->{id%16}
),主键1370304180772954114对16取模为2,走了t_order_2。
当指定了user_id,根据分库的分片算法(db$->{user_id%2}
), 1370295535473528834对2取模,选择了db0库,然后进一步选择了t_order_2表
2021-03-12 22:49:26.170 INFO 4566 --- [ main] ShardingSphere-SQL : Actual SQL: db0 ::: SELECT id,order_no,user_id,item_id,order_price,status,create_time,update_time FROM t_order_2 WHERE id=? ::: [[1370304180772954114]]
2021-03-12 22:49:26.170 INFO 4566 --- [ main] ShardingSphere-SQL : Actual SQL: db1 ::: SELECT id,order_no,user_id,item_id,order_price,status,create_time,update_time FROM t_order_2 WHERE id=? ::: [[1370304180772954114]]
Order(id=1370304180772954114, orderNo=orderNo, userId=1370295535473528834, itemId=1370301749800476674, orderPrice=12.0, status=1, createTime=1615540950252, updateTime=1615540950252)
2021-03-12 22:49:28.259 INFO 4566 --- [ main] ShardingSphere-SQL : Rule Type: sharding
2021-03-12 22:49:28.259 INFO 4566 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,order_no,user_id,item_id,order_price,status,create_time,update_time FROM t_order
WHERE (id = ? AND user_id = ?)
2021-03-12 22:49:28.259 INFO 4566 --- [ main] ShardingSphere-SQL : SQLStatement: SelectStatement(super=DQLStatement(super=io.shardingsphere.core.parsing.parser.sql.dql.select.SelectStatement@205159dc), containStar=false, firstSelectItemStartPosition=8, selectListLastPosition=79, groupByLastPosition=0, items=[CommonSelectItem(expression=id, alias=Optional.absent()), CommonSelectItem(expression=order_no, alias=Optional.absent()), CommonSelectItem(expression=user_id, alias=Optional.absent()), CommonSelectItem(expression=item_id, alias=Optional.absent()), CommonSelectItem(expression=order_price, alias=Optional.absent()), CommonSelectItem(expression=status, alias=Optional.absent()), CommonSelectItem(expression=create_time, alias=Optional.absent()), CommonSelectItem(expression=update_time, alias=Optional.absent())], groupByItems=[], orderByItems=[], limit=null, subQueryStatement=null, subQueryStatements=[], subQueryConditions=[])
2021-03-12 22:49:28.259 INFO 4566 --- [ main] ShardingSphere-SQL : Actual SQL: db0 ::: SELECT id,order_no,user_id,item_id,order_price,status,create_time,update_time FROM t_order_2
WHERE (id = ? AND user_id = ?) ::: [[1370304180772954114, 1370295535473528834]]
Order(id=1370304180772954114, orderNo=orderNo, userId=1370295535473528834, itemId=1370301749800476674, orderPrice=12.0, status=1, createTime=1615540950252, updateTime=1615540950252)
六、代码地址
https://github.com/xushengjun/JAVA-01/tree/main/Week_08/day1/homework2/ecommerce-shardingjdbc