配置连接环境
引入maven 依赖
本文 Sharding-JDBC 选择 4.1.1 版本,可以根据需要选择其他版本。
<!-- MySQL 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<!-- Sharding-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
数据库连接
Sharding-JDBC 可以通过 Java 、YAML、Spring 命名空间 和 Spring Boot Starter 4种方式进行配置,本文选择较为常用的YAML的方式进行配置。
spring:
shardingsphere:
datasource:
names: d1
d1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_name?onnectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
代码编写
Mapper 映射
@Builder
@TableName("drug_info_d")
public class DrugInfoDO {
@TableId
private Integer drugInfoId;
// ...
}
Mapper 映射
@Repository
public interface DrugInfoDAO extends BaseMapper<DrugInfoDO> {
}
Test 类测试
@Log
@SpringBootTest
class ShardingJdbcApplicationTests {
@Autowired
private DrugInfoDAO drugInfoDAO;
@Test
public void testDrugInfo(){
LambdaQueryWrapper<DrugInfoDO> queryWrapper = new LambdaQueryWrapper<>();
List<DrugInfoDO> drugInfoDOS = drugInfoDAO.selectList(queryWrapper);
log.info("药品表数据量 :" + drugInfoDOS.size());
}
}
运行结果
如图可以到数据库配置已经生效,实际执行的语句如下,使用别名为 d1 配置。
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d
ShardingSphere-JDBC 路由
- 分片路由(直接路由、标准路由、笛卡尔路由)
- 广播路由(全库路由、全库表路由、全实例路由、单播路由、阻断路由)
本文主要从数据库、数据库表等方面讲解
inline,standard,complex``````hint等数据库表分片策略。
inline 策略
yml 配置
spring:
shardingsphere:
datasource:
names: d1
d1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/day?onnectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
sharding:
tables:
drug_info_d: # 业务逻辑表
actualDataNodes: d1.drug_info_d${1..3} #实际要查询的表
tableStrategy: # 指定 drug_info_d 表分片策略,分片策略包括分片键和分片算法
inline:
shardingColumn: DRUG_INFO_ID
algorithmExpression: drug_info_d${DRUG_INFO_ID%3 + 1}
keyGenerator: # 指定 drug_info_d 表的主键生成策略为 SNOWFLAKE
type: SNOWFLAKE
column: DRUG_INFO_ID #指定主键
binding-tables: drug_info_d # 绑定逻辑表
props: #
sql:
show: true
main:
allow-bean-definition-overriding: true
配置讲解
- drug_info_d${1..3} 等价于 drug_info_d1、drug_info_d2、drug_info_d3
数据插入
@Test
public void insert(){
for (int i = 0; i< 10; i++) {
DrugInfoDO build = DrugInfoDO.builder().drugInfoId(i).drugName("测试药品").dosformName("测试药品").gennameCode("123").genname("测试药品").build();
drugInfoDAO.insert(build);
}
}
插入数据时,完全按照配置的规则( DRUG_INFO_ID%3 + 1 )执行。
ShardingSphere-SQL : Actual SQL: d1 ::: INSERT INTO drug_info_d3 ( drug_info_id,drug_name,genname_code,genname,dosform_name ) VALUES (?, ?, ?, ?, ?) ::: [5, 测试药品, 123, 测试药品, 测试药品]
ShardingSphere-SQL : Actual SQL: d1 ::: INSERT INTO drug_info_d2 ( drug_info_id,drug_name,genname_code,genname,dosform_name ) VALUES (?, ?, ?, ?, ?) ::: [7, 测试药品, 123, 测试药品, 测试药品]
ShardingSphere-SQL : Actual SQL: d1 ::: INSERT INTO drug_info_d1 ( drug_info_id,drug_name,genname_code,genname,dosform_name ) VALUES (?, ?, ?, ?, ?) ::: [9, 测试药品, 123, 测试药品, 测试药品]
查询数据
@Test
public void insert(){
for (int i = 0; i< 10; i++) {
DrugInfoDO build = DrugInfoDO.builder().drugInfoId(i).drugName("测试药品").dosformName("测试药品").gennameCode("123").genname("测试药品").build();
drugInfoDAO.insert(build);
}
}
执行结果
- ShardingSphere-JDBC 逻辑要执行的语句从 drug_info_d 表中查询数据。
- 实际上是从drug_info_d1、drug_info_d2、drug_info_d3 物理表中查询数据信息。
ShardingSphere-SQL : Logic SQL: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d1
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d2
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d3
以上是使用 ShardingSphere 对数据进行水平分表的新增、查询操作。 当某属性精确/模糊查询时ShardingSphere 会对所有的分表进行遍历查询获取最终的数据信息(可以根据自己需要自行测试)。
【疑问点】当前策略下是否支持范围查询吗
- 不支持。
- 范围查询时,提示 Inline strategy cannot support this type sharding:RangeRouteValue(如下图),需要指定相应的策略执行。
standard 策略
yml 配置
standard_table:
# 使用多数据库d1,d2 多表 standard_table_1, standard_table_2
actualDataNodes: d${1..2}.standard_table_${1..2}
databaseStrategy:
standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
shardingColumn: s_id
# 范围查询
range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
# 精确查询
precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
tableStrategy:
standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
shardingColumn: s_id
# 范围查询
range-algorithm-class-name: com.flyfan.src.algorithm.StandardRangeShading
# 精确查询
precise-algorithm-class-name: com.flyfan.src.algorithm.StandardPreciseSharding
keyGenerator: # 指定 standard_table 表的主键生成策略为 SNOWFLAKE
type: SNOWFLAKE #主键生成策略为 SNOWFLAKE
column: s_id #指定主键
数据库范围查询配置( StandardDBRangeShading )
- 由于范围查询会导致数据分布在不同的数据库表结构中,实际使用时自行根据需要进行配置。
public class StandardDBRangeShading implements RangeShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {
// 获取范围中的上下界(最大、最小值)
Integer uppeValue = rangeShardingValue.getValueRange().upperEndpoint();
rangeShardingValue.getValueRange().lowerEndpoint();
String dbName = rangeShardingValue.getLogicTableName();
// 返回查询时涉及的数据库名称
return Arrays.asList("d1", "d2");
}
}
数据库精确查询配置( StandardDBPreciseSharding )
- 根据需要配置数据查询从指定的数据库中查询。
public class StandardDBPreciseSharding implements PreciseShardingAlgorithm<Integer> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {
String tableName = preciseShardingValue.getLogicTableName();
Integer sIdValue = preciseShardingValue.getValue();
// 实现 d1,d2
BigInteger tableIndex = BigInteger.valueOf(sIdValue);
BigInteger resB = tableIndex.mod(new BigInteger("2")).add(new BigInteger("1"));
String key = "d" + resB;
if (collection.contains(key)){
return key;
}
return null;
}
}
数据表范围查询配置( StandardRangeShading )
- 范围查询导致数据分布在不同的数据表中,因而需要返回数据可能出现的所有数据表
public class StandardRangeShading implements RangeShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {
// 获取范围中的上下界(最大、最小值)
Integer uppeValue = rangeShardingValue.getValueRange().upperEndpoint();
rangeShardingValue.getValueRange().lowerEndpoint();
String tableName = rangeShardingValue.getLogicTableName();
// standard_table_1, standard_table_2
return Arrays.asList(tableName+"_1", tableName+"_2");
}
}
数据表范围查询配置( StandardPreciseSharding )
- 精确到具体的数据表中查询数据信息
public class StandardPreciseSharding implements PreciseShardingAlgorithm<Integer> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {
String tableName = preciseShardingValue.getLogicTableName();
String sId = preciseShardingValue.getColumnName();
Integer sIdValue = preciseShardingValue.getValue();
// 实现 db_$->{s_id%2 +1}
BigInteger tableIndex = BigInteger.valueOf(sIdValue);
BigInteger resB = tableIndex.mod(new BigInteger("2")).add(new BigInteger("1"));
String key = tableName +"_"+resB;
if (collection.contains(key)){
return key;
}
return null;
}
}
数据插入
@Test
public void insert(){
for (int i = 0; i < 20; i++) {
Long value = Long.valueOf(i);
StandardTable build = StandardTable.builder().sId(i).sName("standard_" + i).sNo(i).sContent("standard_table_" + i).build();
standardTableDAO.insert(build);
}
}
执行结果
数据将被均匀的保存在db1.standard_table_1,db2.standard_table_2 中。
查询数据
- 只查询 s_id 为奇数的数据信息。
@Test
public void queryIn(){
LambdaQueryWrapper<StandardTable> queryWrapper = new LambdaQueryWrapper<>();
// 查询 1,3,5
queryWrapper.in(StandardTable::getSId,1,3,5);
List<StandardTable> standardTables = standardTableDAO.selectList(queryWrapper);
standardTables.stream().forEach( st -> {
System.out.println(st);
});
}
-
使用数据库表的精确查询策略。直接查询 db2 中的 standard_table_2 表。
-
范围查询将会导致精确查询失效。
@Test
public void queryRange(){
LambdaQueryWrapper<StandardTable> queryWrapper = new LambdaQueryWrapper<>();
// 使用范围查询数据
queryWrapper.le(StandardTable::getSId,20);
queryWrapper.ge(StandardTable::getSId,15);
List<StandardTable> standardTables = standardTableDAO.selectList(queryWrapper);
standardTables.stream().forEach( st -> {
System.out.println(st);
});
}
- 范围查询将会根据配置扫描所有的数据库下的数据表。
complex 策略
当数据表中有某个键具有跟主分片键相同的特性时,查询时可以根据已有的主分片建,对额外的分片键进行处理,来实现指定的数据库、数据表进行数据的查询优化工作。
yml 配置
complex_table:
actualDataNodes: d${1..2}.complex_table_${1..2}
databaseStrategy:
standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
shardingColumn: c_id
range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
tableStrategy:
complex:
# 分片键c_id,
sharding_columns: c_id,c_no
algorithm-class-name: com.flyfan.src.algorithm.ComplexTableShading
表分片策略配置 ( ComplexTableShading )
public class ComplexTableShading implements ComplexKeysShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {
// 范围
Range<Integer> cIdRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("c_id");
Collection<Integer> cNoCol = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("c_no");
Integer upperValue = cIdRange.upperEndpoint();
Integer lowerValue = cIdRange.lowerEndpoint();
List<String> result = new ArrayList<>();
for (Integer cNo : cNoCol) {
//
BigInteger index = BigInteger.valueOf(cNo).mod(new BigInteger("2")).add(new BigInteger("1"));
result.add(complexKeysShardingValue.getLogicTableName() + "_" + index);
}
log.info(result.toString());
return result;
}
}
查询数据
@Test
public void queryIn(){
LambdaQueryWrapper<ComplexTable> queryWrapper = new LambdaQueryWrapper<>();
// 范围查询
queryWrapper.between(ComplexTable::getCId,6,15);
// 精确查询
queryWrapper.eq(ComplexTable::getCNo,12);
for (ComplexTable complexTable : complexTableDAO.selectList(queryWrapper)) {
System.out.println(complexTable);
}
}
- 范围查询的同时精确的在 complex_table_1 中进行查询操作。
hint 策略
yml 配置
hint_table:
actualDataNodes: d${1..2}.hint_table_${1..2}
databaseStrategy:
standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
shardingColumn: h_id
range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
tableStrategy:
hint: # 指定 hint_table 表的分片策略,分片策略包括分片键和分片算法
algorithm-class-name: com.flyfan.src.algorithm.HintTableShading
表分片策略配置 ( ComplexTableShading )
public class HintTableShading implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {
// 获取指定的表名后缀值
Collection<Integer> tableShardingValue = hintShardingValue.getValues();
String key = hintShardingValue.getLogicTableName() + "_" + tableShardingValue.toArray()[0];
if (collection.contains(key)){
return Arrays.asList(key);
}
return null;
}
}
查询数据
@Test
public void queryByHint(){
HintManager hintManager = HintManager.getInstance();
// 设置从指定的数据表中查询数据
hintManager.addTableShardingValue("hint_table",2);
LambdaQueryWrapper<HintTable> queryWrapper = new LambdaQueryWrapper<>();
log.info("查询到的数据大小为:" + hintTableDAO.selectList(queryWrapper).size());
hintManager.close();
}
从指定的数据表 hint_table_2 中查询
总结
本文介绍了 inline、standard、complex、hint 等策略的简单使用,读者可以通过该篇文章快速了解ShardingSphere-JDBC 使用,方便根据实际业务需要进行数据的分库分表来解决遇到的问题。