springboot整合shardingjdbc實(shí)現(xiàn)分庫(kù)分表最簡(jiǎn)單demo
一、概覽
1.1 簡(jiǎn)介
ShardingSphere-JDBC定位為輕量級(jí) Java 框架,在 Java 的 JDBC 層提供的額外服務(wù)。 它使用客戶端直連數(shù)據(jù)庫(kù),以 jar 包形式提供服務(wù),無(wú)需額外部署和依賴,可理解為增強(qiáng)版的 JDBC 驅(qū)動(dòng),完全兼容 JDBC 和各種 ORM 框架。
- 適用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
- 支持任何第三方的數(shù)據(jù)庫(kù)連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
- 支持任意實(shí)現(xiàn) JDBC 規(guī)范的數(shù)據(jù)庫(kù),目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標(biāo)準(zhǔn)的數(shù)據(jù)庫(kù)。

1.2 對(duì)比

| 名稱 | ShardingSphere-JDBC | ShardingSphere-Proxy | ShardingSphere-Sidecar |
|---|---|---|---|
| 數(shù)據(jù)庫(kù) | 任意 | MySQL/PostgreSQL | MySQL/PostgreSQL |
| 連接消耗數(shù) | 高 | 低 | 高 |
| 異構(gòu)語(yǔ)言 | 僅 Java | 任意 | 任意 |
| 性能 | 損耗低 | 損耗略高 | 損耗低 |
| 無(wú)中心化 | 是 | 否 | 是 |
| 靜態(tài)入口 | 無(wú) | 有 | 無(wú) |
1.3 分庫(kù)分表場(chǎng)景
隨著時(shí)間和業(yè)務(wù)的發(fā)展,造成表里面的數(shù)據(jù)越來(lái)越多,如果再去對(duì)數(shù)據(jù)庫(kù)表curd操作,很容易造成性能問(wèn)題。為了解決由于數(shù)據(jù)量過(guò)大而造成數(shù)據(jù)庫(kù)性能降低的問(wèn)題,常見(jiàn)的解決方案如下:
- 從硬件上增加數(shù)據(jù)庫(kù)服務(wù)器的存儲(chǔ),
- 分庫(kù)分表處理
分庫(kù)分表又可以分為水平分表、水平分庫(kù)、垂直分表、垂直分庫(kù)

水平分表
特點(diǎn):
每個(gè)表的結(jié)構(gòu)都一樣;
每個(gè)表的數(shù)據(jù)都不一樣,沒(méi)有交集;
所有表的并集是該表的全量數(shù)據(jù);場(chǎng)景:?jiǎn)伪淼臄?shù)據(jù)量過(guò)大或增長(zhǎng)速度很快,已經(jīng)影響或即將會(huì)影響SQL查詢效率,加重了CPU負(fù)擔(dān),提前到達(dá)瓶頸。
水平分庫(kù)
特點(diǎn):
每個(gè)庫(kù)的結(jié)構(gòu)都一樣;
每個(gè)庫(kù)的數(shù)據(jù)都不一樣,沒(méi)有交集;
所有庫(kù)的并集是全量數(shù)據(jù);場(chǎng)景:系統(tǒng)絕對(duì)并發(fā)量上來(lái)了,CPU內(nèi)存壓力大。分表難以根本上解決量的問(wèn)題,并且還沒(méi)有明顯的業(yè)務(wù)歸屬來(lái)垂直分庫(kù),主庫(kù)磁盤接近飽和。
垂直分表
特點(diǎn):
每個(gè)表的結(jié)構(gòu)都不一樣;
每個(gè)表的數(shù)據(jù)也不一樣,
有一個(gè)關(guān)聯(lián)字段,一般是主鍵或外鍵,用于關(guān)聯(lián)兄弟表數(shù)據(jù);
所有兄弟表的并集是該表的全量數(shù)據(jù);場(chǎng)景:
有幾個(gè)字段屬于熱點(diǎn)字段,更新頻率很高,要把這些字段單獨(dú)切到一張表里,不然innodb行鎖很惡心的
有大字段,如text,存儲(chǔ)壓力很大,畢竟innodb數(shù)據(jù)和索引是同一個(gè)文件;同時(shí),我又喜歡用SELECT *,你懂得,這磁盤IO消耗的,跟玩兒似的,誰(shuí)都扛不住的。
垂直分庫(kù)
縱向切庫(kù)基于表進(jìn)行切分,類似多數(shù)據(jù)源,通常是把新的業(yè)務(wù)模塊或集成公共模塊拆分出去,比如我們最熟悉的單點(diǎn)登錄、鑒權(quán)模塊。
1.4 非分片表處理方法
我們知道分庫(kù)分表是針對(duì)某些數(shù)據(jù)量持續(xù)大幅增長(zhǎng)的表,比如用戶表、訂單表等,而不是一刀切將全部表都做分片。那么不分片的表和分片的表如何劃分,一般有兩種解決方案。
- 嚴(yán)格劃分功能庫(kù),分片的庫(kù)與不分片的庫(kù)剝離開,業(yè)務(wù)代碼中按需切換數(shù)據(jù)源訪問(wèn)
- 默認(rèn)數(shù)據(jù)源,以 Sharding-JDBC 為例,不給未分片表設(shè)置分片規(guī)則,它們就不會(huì)執(zhí)行,因?yàn)檎也坏铰酚梢?guī)則,如果我們?cè)O(shè)置一個(gè)默認(rèn)數(shù)據(jù)源,在找不到規(guī)則時(shí)一律訪問(wèn)默認(rèn)庫(kù)。
# 配置數(shù)據(jù)源 m1 spring.shardingsphere.datasource.name=m1 spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.m1.driverClassName=com.mysql.jdbc.Driver spring.shardingsphere.datasource.m1.url=jdbc:mysql://xxxx:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT spring.shardingsphere.datasource.m1.username=root spring.shardingsphere.datasource.m1.password=xxxx # 默認(rèn)數(shù)據(jù)源,未分片的表默認(rèn)執(zhí)行庫(kù) spring.shardingsphere.sharding.default-data-source-name=m1
1.5 技術(shù)棧
- SpringBoot2.3.8.RELEASE
- MyBatis-Plus3.4.0
- Sharding-JDBC
- Druid連接池
二、 項(xiàng)目整合
2.1 pom.xml
<!-- shardingjdbc依賴包 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<!-- 連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 分布式事務(wù)所需包 -->
<!-- 使用 XA 事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>4.1.1</version>
</dependency>
<!-- <!– 使用 BASE 事務(wù)時(shí),需要引入此模塊 –>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.shardingsphere</groupId>-->
<!-- <artifactId>sharding-transaction-base-seata-at</artifactId>-->
<!-- <version>4.1.1</version>-->
<!-- </dependency>-->
<!-- <!– https://mvnrepository.com/artifact/io.seata/seata-core –>-->
<!-- <dependency>-->
<!-- <groupId>io.seata</groupId>-->
<!-- <artifactId>seata-core</artifactId>-->
<!-- <version>1.4.2</version>-->
<!-- </dependency>-->
注意:如果原有項(xiàng)目引入了 druid包 以及多數(shù)據(jù)源包dynamic-datasource-spring-boot-starter,需要注釋掉相關(guān)引用
<!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter --> <!-- <dependency>--> <!-- <groupId>com.baomidou</groupId>--> <!-- <artifactId>dynamic-datasource-spring-boot-starter</artifactId>--> <!-- <version>3.1.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>com.alibaba</groupId>--> <!-- <artifactId>druid-spring-boot-starter</artifactId>--> <!-- </dependency>-->
2.2 jpa/mybatis項(xiàng)目其他調(diào)整 springboot啟動(dòng)類增加如下配置
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
分片表ORM映射實(shí)體類注釋表名映射

數(shù)據(jù)庫(kù)鏈接賬號(hào)賦予分布式事務(wù)XA權(quán)限
GRANT XA_RECOVER_ADMIN ON *.* TO root@'%'
配置springboot數(shù)據(jù)源健康檢查sql(可選)
package com.yss.datamiddle.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.Map;
/**
* @description: 重寫健康檢查sql,解決項(xiàng)目啟動(dòng)健康檢查異常
* @author: Han LiDong
* @create: 2021/5/28 14:40
* @update: 2021/5/28 14:40
*/
@Configuration
public class DataSourceHealthConfig extends DataSourceHealthContributorAutoConfiguration {
private static final String defaultQuery = "select 1";
public DataSourceHealthConfig(Map<String, DataSource> dataSources, ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
super(dataSources, metadataProviders);
}
@Override
protected AbstractHealthIndicator createIndicator(DataSource source) {
DataSourceHealthIndicator indicator = (DataSourceHealthIndicator) super.createIndicator(source);
if (!StringUtils.hasText(indicator.getQuery())) {
indicator.setQuery(defaultQuery);
}
return indicator;
}
}
三、分庫(kù)分表實(shí)現(xiàn)
3.1 水平分表-單分片鍵(標(biāo)準(zhǔn)分片算法、自定義分布式主鍵生成算法)
創(chuàng)建表單course_1,course_2
約定規(guī)則:如果添加的主鍵ID是偶數(shù)把數(shù)據(jù)添加進(jìn)course_1表,如果是奇數(shù)添加進(jìn)course_2表
-- ---------------------------- -- Table structure for course_1 -- ---------------------------- DROP TABLE IF EXISTS `course_1`; CREATE TABLE `course_1` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài)', `create_time` date DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for course_2 -- ---------------------------- DROP TABLE IF EXISTS `course_2`; CREATE TABLE `course_2` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài)', `create_time` date DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
application-table-horizontal.yml配置分表規(guī)則
#水平分表配置
spring:
main:
#允許名稱相同的bean的覆蓋(一個(gè)實(shí)體類對(duì)應(yīng)多張表)
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔(m1,m2)
names: m1
#names定義的數(shù)據(jù)源名稱作為key(key不能包含下劃線,否則無(wú)法識(shí)別配置)
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
# rules:
sharding:
tables:
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
# 由數(shù)據(jù)源名 + 表名組成,以小數(shù)點(diǎn)分隔。多個(gè)表以逗號(hào)分隔,支持inline表達(dá)式。缺省表示使用已知數(shù)據(jù)源與邏輯表名稱生成數(shù)據(jù)節(jié)點(diǎn),用于廣播表(即每個(gè)庫(kù)中都需要一個(gè)同樣的表用于關(guān)聯(lián)查詢,多為字典表)或只分庫(kù)不分表且所有庫(kù)的表結(jié)構(gòu)完全一致的情況
actual-data-nodes: m1.course_$->{1..2}
#分庫(kù)策略:?jiǎn)畏制I
table-strategy:
inline:
#分片鍵
sharding-column: id
#數(shù)據(jù)分片規(guī)則(ID是偶數(shù)把數(shù)據(jù)添加入course_1,奇數(shù)入course_2)
algorithm-expression: course_$->{id % 2 + 1}
測(cè)試-分表-新增
/**
* 測(cè)試分表-新增
*/
@Test
public void addCourse() {
for (int i = 0; i < 10; i++) {
Course course = new Course();
course.setName("java" + i);
course.setStatus(1);
course.setCreateTime(new Date());
courseMapper.insert(course);
}
}

測(cè)試-分表-查詢
/**
* 查詢分表數(shù)據(jù)
*/
@Test
public void findCourse() {
//分區(qū)字段查詢數(shù)據(jù):精準(zhǔn)匹配分片表,不會(huì)去別的表中掃描數(shù)據(jù)
Course course = courseMapper.selectById(Long.valueOf("607168187053637632"));
log.info(course.toString());
//非分區(qū)字段查詢:全表匹配,匯總結(jié)果
QueryWrapper<Course> queryWrapper2 = new QueryWrapper<Course>();
queryWrapper2.between("create_time",
DateUtil.stringToDate("2021-01-26 11:39:05"),
DateUtil.stringToDate("2021-07-26 11:39:05"));
List<Course> list2 = courseMapper.selectList(queryWrapper2);
log.info("數(shù)據(jù)量{}",list2.size());
}

由上可以看出分片字段作為查詢條件時(shí),請(qǐng)準(zhǔn)定位分片數(shù)據(jù)所在分片表。非分片字段查詢時(shí),全表匹配,匯總結(jié)果
自定義分布式主鍵生成算法
實(shí)現(xiàn)ShardingKeyGenerator接口,自定義分布式主鍵生成算法
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;
import org.springframework.stereotype.Component;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
/**
* @description: 自定義sharding-jdbc主鍵生成算法
* @author: Han LiDong
* @create: 2021/5/25 09:36
* @update: 2021/5/25 09:36
*/
@Component
public class SimpleShardingKeyGenerator implements ShardingKeyGenerator {
private AtomicLong atomic = new AtomicLong(0);
@Getter
@Setter
private Properties properties = new Properties();
/**
* 分布式主鍵實(shí)現(xiàn)算法。
*/
@Override
public Comparable<?> generateKey() {
return atomic.incrementAndGet();
}
@Override
public String getType() {
//聲明類型,需要在配置文件中配置此key
return "SIMPLE";
}
}
resources下配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator

配置主鍵生成策略為自定義key

3.2 水平分表-單分片鍵-按照月份分表(標(biāo)準(zhǔn)分片算法)
創(chuàng)建course_202101到course_202108表單
約定規(guī)則:按照創(chuàng)建時(shí)間對(duì)應(yīng)的yyyyMM將數(shù)據(jù)分片到不同的表中
-- ---------------------------- -- 表名自己調(diào)整,創(chuàng)建202101-202112的表單 -- ---------------------------- DROP TABLE IF EXISTS `course_202101`; CREATE TABLE `course_202101` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài)', `create_time` datetime(0) DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
application-table-time-horizontal.yml配置月份分表規(guī)則
#按照月份自定義水平分表策略配置
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1
m1:
password: xxxx
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
# rules:
sharding:
tables:
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
type: SNOWFLAKE
##配置 t_order 表規(guī)則 ->{a..b} a必須存在,否則分布式主鍵無(wú)法獲取
actual-data-nodes: m1.course_$->{2021..2200}0$->{1..9},m1.course_$->{2021..2200}$->{10..12}
table-strategy:
standard:
#精確分片算法類名稱,用于 = 和 IN。該類需實(shí)現(xiàn)PreciseShardingAlgorithm 接口并提供無(wú)參數(shù)的構(gòu)造器
precise-algorithm-class-name: com.xlhj.sharding.config.CoursePreciseShardingAlgorithm
# 范圍分片算法類名稱,用于 BETWEEN,可選。該類需實(shí)現(xiàn)RangeShardingAlgorithm 接口并提供無(wú)參數(shù)的構(gòu)造器
range-algorithm-class-name: com.xlhj.sharding.config.TableRangeShardAlgorithm
# 分片字段
sharding-column: create_time
精準(zhǔn)分片算法實(shí)現(xiàn)
package com.xlhj.sharding.config;
import com.xlhj.sharding.util.DateUtil;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Date;
/**
* @description: 精準(zhǔn)分片算法類
* @author: Han LiDong
* @create: 2021/5/25 10:32
* @update: 2021/5/25 10:32
*/
@Component
public class CoursePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
/**
* 按照 tablename_yyyyMM進(jìn)行分表 用于 = in等
* @param collection
* @param preciseShardingValue
* @return
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
StringBuffer tableName = new StringBuffer();
tableName.append(preciseShardingValue.getLogicTableName())
.append("_").append(DateUtil.dateToString(preciseShardingValue.getValue(),"yyyyMM");
return tableName.toString();
}
}
范圍分片算法實(shí)現(xiàn)
package com.xlhj.sharding.config;
import com.google.common.collect.Range;
import com.xlhj.sharding.util.DateUtil;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @description: 范圍分片算法類 用于 BETWEEN等
* @author: Han LiDong
* @create: 2021/5/25 10:32
* @update: 2021/5/25 10:32
*/
@Component
public class TableRangeShardAlgorithm implements RangeShardingAlgorithm<Date> {
private static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 分片鍵日期范圍包含分片表名稱集合
* @param availableTargetNames
* @param rangeShardingValue
* @return
*/
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) {
System.out.println("范圍-*-*-*-*-*-*-*-*-*-*-*---------------" + availableTargetNames);
System.out.println("范圍-*-*-*-*-*-*-*-*-*-*-*---------------" + rangeShardingValue);
//物理表名集合
//Collection<String> tables = new LinkedHashSet<>();
//邏輯表名
String logicTableName = rangeShardingValue.getLogicTableName();
//分片鍵的值
Range<Date> valueRange = rangeShardingValue.getValueRange();
Date lowerEndpoint = valueRange.lowerEndpoint();
Date upperEndpoint = valueRange.upperEndpoint();
List<String> YMList = DateUtil.getYMBetweenDate(lowerEndpoint,upperEndpoint);
List<String> tables = YMList.stream().map( ym ->{
return logicTableName + "_" + ym;
}).collect(Collectors.toList());
return tables;
}
}
測(cè)試-日期分表-新增
/**
* 測(cè)試分表
*/
@Test
public void addCourse() {
for (int i = 0; i < 10; i++) {
Course course = new Course();
course.setName("java" + i);
course.setStatus(1);
course.setCreateTime(new Date());
courseMapper.insert(course);
}
}

3.3 水平分表-多分片鍵(復(fù)合分片算法)
繼續(xù)使用course_1、course_2表單
約定規(guī)則:如果添加的主鍵ID是偶數(shù)把數(shù)據(jù)添加進(jìn)course_1表,如果是奇數(shù)添加進(jìn)course_2表
application-table-horizontal-columns.yml配置多分片鍵分表規(guī)則
#水平分表配置
spring:
main:
#允許名稱相同的bean的覆蓋(一個(gè)實(shí)體類對(duì)應(yīng)多張表)
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1
# names定義的數(shù)據(jù)源名稱作為key(key不能包含下劃線,否則無(wú)法識(shí)別配置)
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
sharding:
tables:
# 表名
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SIMPLE
#由數(shù)據(jù)源名 + 表名組成,以小數(shù)點(diǎn)分隔。多個(gè)表以逗號(hào)分隔,支持inline表達(dá)式。
#缺省表示使用已知數(shù)據(jù)源與邏輯表名稱生成數(shù)據(jù)節(jié)點(diǎn),用于廣播表(即每個(gè)庫(kù)中都需要一個(gè)同樣的表用于關(guān)聯(lián)查詢,多為字典表)或只分庫(kù)不分表且所有庫(kù)的表結(jié)構(gòu)完全一致的情況
actual-data-nodes: m1.course_$->{1..2}
#分片策略:多分片鍵
table-strategy:
complex:
# 分片鍵
sharding-columns: id,status
# 自定義分片算法
algorithm-class-name: com.xlhj.sharding.config.CourseShardingAlgorithmColumns
自定義分片算法實(shí)現(xiàn)
package com.xlhj.sharding.config;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @description: 分表算法類-多分片鍵
* @author: Han LiDong
* @create: 2021/5/25 10:32
* @update: 2021/5/25 10:32
*/
@Component
public class CourseShardingAlgorithmColumns implements ComplexKeysShardingAlgorithm {
/**
*
* @param collection 分片表名
* @param shardingValues 分片字段值
* @return
*/
@Override
public Collection<String> doSharding(Collection collection, ComplexKeysShardingValue shardingValues) {
System.out.println("collection:" + collection + ",shardingValues:" + shardingValues);
Map<String, Collection> map = shardingValues.getColumnNameAndShardingValuesMap();
Collection<Long> idValues = map.get("id");
Collection<Integer> statusValues = map.get("status");
List<String> shardingSuffix = new ArrayList<>();
//邏輯還是按照 id%2 + 1進(jìn)行數(shù)據(jù)分片
for (Long id : idValues) {
Long suf = id % 2 + 1;
for (Object s : collection) {
String tableName = (String) s;
// 分片表名后綴匹配
if (tableName.endsWith(String.valueOf(suf))) {
shardingSuffix.add(tableName);
}
}
}
return shardingSuffix;
}
}
測(cè)試-多分片鍵-新增
/**
* 測(cè)試分表-新增
*/
@Test
public void addCourse() {
for (int i = 0; i < 10; i++) {
Course course = new Course();
course.setName("java" + i);
course.setStatus(1);
course.setCreateTime(new Date());
courseMapper.insert(course);
}
}

3.4 水平分庫(kù)+分表-單分片鍵
另找一個(gè)數(shù)據(jù)庫(kù)創(chuàng)建表單course_1,course_2
約定規(guī)則:根據(jù)status=0數(shù)據(jù)到庫(kù)1,status=1數(shù)據(jù)到庫(kù)2. id為奇數(shù)到course_2表,偶數(shù)到course_1表
-- ---------------------------- -- 在庫(kù)2中創(chuàng)建如下表單 -- ---------------------------- DROP TABLE IF EXISTS `course_1`; CREATE TABLE `course_1` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài) 0:失效 1:有效', `create_time` date DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `course_2`; CREATE TABLE `course_2` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài)', `create_time` date DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
application-database-horizontal.yml配置分庫(kù)分表規(guī)則
#水平分庫(kù)、分表配置
spring:
main:
#允許名稱相同的bean的覆蓋
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1,m2
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
m2:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
# rules:
sharding:
tables:
# 表名
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
#配置 course 表規(guī)則groovy語(yǔ)法 $->{a..b}
actual-data-nodes: m$->{1..2}.course_$->{1..2}
#分庫(kù)規(guī)則
database-strategy:
inline:
#分庫(kù)字段
sharding-column: status
#數(shù)據(jù)分庫(kù)規(guī)則
algorithm-expression: m$->{status + 1}
#分表規(guī)則
table-strategy:
inline:
#分表字段
sharding-column: id
#數(shù)據(jù)分表規(guī)則
algorithm-expression: course_$->{id % 2 + 1}
測(cè)試-分庫(kù)分表-新增
/**
* 測(cè)試水平分庫(kù)+分表
*/
@Test
public void addCourseDB() {
for (int i = 0; i < 10; i++) {
Course course = new Course();
course.setName("java");
int rand = (int)(Math.random() * 10);
course.setStatus(rand % 2);
course.setCreateTime(new Date());
courseMapper.insert(course);
}
}

3.5 水平分庫(kù)+分表-Hint分片(強(qiáng)制分片路由)
庫(kù)2 創(chuàng)建course_yyyyMM相關(guān)表單
約定規(guī)則:查詢/新增數(shù)據(jù)的時(shí)候指定分片路由,強(qiáng)制路由到某張表
-- ---------------------------- -- 庫(kù)2創(chuàng)建表單,表名自己調(diào)整,創(chuàng)建202101-202112的表單 -- ---------------------------- DROP TABLE IF EXISTS `course_202101`; CREATE TABLE `course_202101` ( `id` bigint(20) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '課程名稱', `status` int(255) DEFAULT NULL COMMENT '狀態(tài)', `create_time` datetime(0) DEFAULT NULL COMMENT '創(chuàng)建日期', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
application-table-hint-horizontal.yml配置強(qiáng)制路由規(guī)則
#強(qiáng)制分片路由hint配置
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1,m2
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
m2:
password: xxxx
url: jdbc:mysql://182.92.219.202:3306/sharding_db-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
# rules:
sharding:
tables:
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
#配置 t_order 表規(guī)則 ->{a..b} a必須存在,否則分布式主鍵無(wú)法獲取
actual-data-nodes: m1.course_$->{2021..2200}0$->{1..9},m1.course_$->{2021..2200}$->{10..12}
database-strategy:
hint:
# 自定義分庫(kù)hit分片算法
algorithm-class-name: com.xlhj.sharding.config.DatabaseHintShardingKeyAlgorithm
table-strategy:
hint:
# 自定義分表hit分片算法
algorithm-class-name: com.xlhj.sharding.config.TableHintShardingKeyAlgorithm
自定義強(qiáng)制分庫(kù)路由算法實(shí)現(xiàn)
package com.xlhj.sharding.config;
import com.alibaba.druid.util.StringUtils;
import org.apache.shardingsphere.api.sharding.ShardingValue;
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* @description:
* @author: Han LiDong
* @create: 2021/5/27 09:53
* @update: 2021/5/27 09:53
*/
@Component
public class DatabaseHintShardingKeyAlgorithm implements HintShardingAlgorithm {
/**
* 自定義Hint 實(shí)現(xiàn)算法
* 能夠保證繞過(guò)Sharding-JDBC SQL解析過(guò)程
* @param availableTargetNames
* @param hintShardingValue 不再?gòu)腟QL 解析中獲取值,而是直接通過(guò)hintManager.addTableShardingValue("t_order", 1)參數(shù)指定
* @return
*/
@Override
public Collection<String> doSharding(Collection availableTargetNames, HintShardingValue hintShardingValue) {
System.out.println("shardingValue=" + hintShardingValue);
System.out.println("availableTargetNames=" + availableTargetNames);
List<String> shardingResult = new ArrayList<>();
Iterator i = availableTargetNames.iterator();
while (i.hasNext()){
String targetName = (String) i.next();
String suffix = targetName.substring(targetName.length() - 1);
if (StringUtils.isNumber(suffix)) {
// hint分片算法的ShardingValue有兩種具體類型:
// ListShardingValue和RangeShardingValue
// 使用哪種取決于HintManager.addDatabaseShardingValue(String, String, ShardingOperator,...),ShardingOperator的類型
Iterator j = hintShardingValue.getValues().iterator();
while (j.hasNext()){
Integer value = (Integer) j.next();
if (value % 2 + 1 == Integer.parseInt(suffix)) {
shardingResult.add(targetName);
}
}
}
}
return shardingResult;
}
}
自定義強(qiáng)制分表路由算法實(shí)現(xiàn)
package com.xlhj.sharding.config;
import com.alibaba.druid.util.StringUtils;
import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* @description:
* @author: Han LiDong
* @create: 2021/5/27 09:53
* @update: 2021/5/27 09:53
*/
@Component
public class TableHintShardingKeyAlgorithm implements HintShardingAlgorithm {
/**
* 自定義Hint 實(shí)現(xiàn)算法
* 能夠保證繞過(guò)Sharding-JDBC SQL解析過(guò)程
* @param availableTargetNames
* @param hintShardingValue 不再?gòu)腟QL 解析中獲取值,而是直接通過(guò)hintManager.addTableShardingValue("t_order", 1)參數(shù)指定
* @return
*/
@Override
public Collection<String> doSharding(Collection availableTargetNames, HintShardingValue hintShardingValue) {
System.out.println("shardingValue=" + hintShardingValue);
System.out.println("availableTargetNames=" + availableTargetNames);
List<String> shardingResult = new ArrayList<>();
Iterator i = availableTargetNames.iterator();
while (i.hasNext()){
String targetName = (String) i.next();
String suffix = targetName.substring(targetName.length() - 1);
if (StringUtils.isNumber(suffix)) {
// hint分片算法的ShardingValue有兩種具體類型:
// ListShardingValue和RangeShardingValue
// 使用哪種取決于HintManager.addDatabaseShardingValue(String, String, ShardingOperator,...),ShardingOperator的類型
Iterator j = hintShardingValue.getValues().iterator();
while (j.hasNext()){
Integer value = (Integer) j.next();
// 匹配月份
Integer month = value % 12 == 0 ? 12 : value;
if (month == Integer.parseInt(suffix)) {
shardingResult.add(targetName);
}
}
}
}
return shardingResult;
}
}
測(cè)試-強(qiáng)制路由
/**
* hint分片算法測(cè)試
* @throws Exception
*/
@Test
public void shardingHintDB() throws Exception {
HintManager.clear();
HintManager hintManager = HintManager.getInstance();
// 方式1:
// 下面2句話的意思時(shí): 向3號(hào)庫(kù)中的1號(hào) course 表執(zhí)行sql
// 選擇具體的數(shù)據(jù)庫(kù), 3 可以簡(jiǎn)單理解為: 3號(hào)庫(kù),如果只有2個(gè)庫(kù), 那么可以根據(jù)2取模+1,落到 2號(hào)庫(kù)上面
hintManager.addDatabaseShardingValue("course", 3);
// 同理:一個(gè)數(shù)據(jù)庫(kù)中可以有多張courser表, 2 可以理解為: 2月份相關(guān)表.
hintManager.addTableShardingValue("course", 2);
// 方式2
// 直接指定對(duì)應(yīng)具體的數(shù)據(jù)庫(kù),會(huì)想此庫(kù)里所有分片表添加數(shù)據(jù)
//hintManager.setDatabaseShardingValue(0);
Course course = new Course();
course.setName("java");
int rand = (int)(Math.random() * 10);
course.setStatus(rand % 2);
course.setCreateTime(new Date());
courseMapper.insert(course);
HintManager.clear();
}

3.6 垂直分表
單庫(kù)垂直分表相當(dāng)于 同一個(gè)庫(kù)的多張表單 通過(guò)外鍵關(guān)聯(lián)。
分庫(kù)垂直分表相當(dāng)于多數(shù)據(jù)源。
這幾介紹下單庫(kù)垂直分表配置:
主要規(guī)則:
#垂直分庫(kù)需要直接指定到庫(kù)和表 spring.shardingsphere.sharding.tables.sys_user.actual-data-nodes: m2.sys_user
詳細(xì)配置:
#垂直分表策略配置
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1,m2
m1:
password: xxxx
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
m2:
password: xxxx
url: jdbc:mysql://182.92.219.202:3306/sharding_db-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
sharding:
tables:
sys_user:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
#垂直分庫(kù) 做到專庫(kù)專表 指定到具體庫(kù).具體表
actual-data-nodes: m2.sys_user
3.7 廣播表
指所有的分片數(shù)據(jù)源中都存在的表,表結(jié)構(gòu)和表中的數(shù)據(jù)在每個(gè)數(shù)據(jù)庫(kù)中均完全一致。適用于數(shù)據(jù)量不大且需要與海量數(shù)據(jù)的表進(jìn)行關(guān)聯(lián)查詢的場(chǎng)景,例如:字典表。
庫(kù)1和庫(kù)2創(chuàng)建字典表t_dict
-- ---------------------------- -- Table structure for t_dict -- ---------------------------- DROP TABLE IF EXISTS `t_dict`; CREATE TABLE `t_dict` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `dic_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '編碼', `dic_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名', `dic_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典值', `pcode` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '父編碼', `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '0:失效 1:生效', `dic_sort` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '排序', `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '備注', `create_time` datetime(0) DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 604343147190812673 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
配置廣播表規(guī)則
主要規(guī)則:
# 配置廣播表表名 spring.shardingsphere.sharding.broadcast-tables: t_dict
詳細(xì)配置:
#廣播表配置
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1,m2
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
m2:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
password: xxxx
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
sharding:
tables:
t_dict:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
# 配置廣播表
broadcast-tables: t_dict
測(cè)試-廣播表
/**
* 測(cè)試公共表
*/
@Test
public void addDict() {
TDict dict = new TDict();
dict.setCreateTime(new Date());
dict.setDicCode("test");
dict.setDicName("test");
dict.setDicSort("1");
dict.setDicValue("test");
dict.setPcode("0");
dict.setStatus("1");
dictMapper.insert(dict);
}

3.8 綁定表
概念:指分片規(guī)則一致的主表和子表。例如:course表和 course_detail表,均按照 course_id分片,則此兩張表互為綁定表關(guān)系。綁定表之間的多表關(guān)聯(lián)查詢不會(huì)出現(xiàn)笛卡爾積關(guān)聯(lián),關(guān)聯(lián)查詢效率將大大提升。
舉例說(shuō)明,如果 SQL 為:
select * from course c left join course_detail cd on c.id = cd.course_id where c.id in (10, 11);
在不配置綁定表關(guān)系時(shí),假設(shè)分片鍵 course_id將數(shù)值 10 路由至第 0 片,將數(shù)值 11 路由至第 1 片,那么路由后的 SQL 應(yīng)該為 4 條,它們呈現(xiàn)為笛卡爾積:
select * from course_1 c left join course_detail_1 cd on c.id = cd.course_id where c.id in (10, 11); select * from course_1 c left join course_detail_2 cd on c.id = cd.course_id where c.id in (10, 11); select * from course_2 c left join course_detail_1 cd on c.id = cd.course_id where c.id in (10, 11); select * from course_2 c left join course_detail_2 cd on c.id = cd.course_id where c.id in (10, 11);
在配置綁定表關(guān)系后,路由的 SQL 應(yīng)該為 2 條:
select * from course_1 c left join course_detail_1 cd on c.id = cd.course_id where c.id in (10, 11); select * from course_2 c left join course_detail_2 cd on c.id = cd.course_id where c.id in (10, 11);
其中 course 在 FROM 的最左側(cè),ShardingSphere 將會(huì)以它作為整個(gè)綁定表的主表。 所有路由計(jì)算將會(huì)只使用主表的策略,那么 course_detail表的分片計(jì)算將會(huì)使用 course 的條件。故綁定表之間的分區(qū)鍵要完全相同。
庫(kù)1創(chuàng)建course_detail_1、course_detail_2表單
-- ---------------------------- -- Table structure for course_detail_1 -- ---------------------------- DROP TABLE IF EXISTS `course_detail_1`; CREATE TABLE `course_detail_1` ( `id` bigint(20) NOT NULL, `course_id` bigint(20) DEFAULT NULL COMMENT '課程id', `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '備注', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for course_detail_2 -- ---------------------------- DROP TABLE IF EXISTS `course_detail_2`; CREATE TABLE `course_detail_2` ( `id` bigint(20) NOT NULL, `course_id` bigint(20) DEFAULT NULL COMMENT '課程id', `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '備注', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
配置廣播表規(guī)則
#水平分表配置
spring:
main:
#允許名稱相同的bean的覆蓋(一個(gè)實(shí)體類對(duì)應(yīng)多張表)
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
show: true
datasource:
# 數(shù)據(jù)源名稱,多數(shù)據(jù)源以逗號(hào)分隔
names: m1
# names定義的數(shù)據(jù)源名稱作為key(key不能包含下劃線,否則無(wú)法識(shí)別配置)
m1:
url: jdbc:mysql://182.92.219.202:3306/sharding_db-1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# 數(shù)據(jù)庫(kù)連接池類名稱 com.alibaba.druid.pool.DruidDataSource com.zaxxer.hikari.HikariDataSource
type: com.alibaba.druid.pool.DruidDataSource
sharding:
tables:
# 表名
course:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
#由數(shù)據(jù)源名 + 表名組成,以小數(shù)點(diǎn)分隔。多個(gè)表以逗號(hào)分隔,支持inline表達(dá)式。
#缺省表示使用已知數(shù)據(jù)源與邏輯表名稱生成數(shù)據(jù)節(jié)點(diǎn),用于廣播表(即每個(gè)庫(kù)中都需要一個(gè)同樣的表用于關(guān)聯(lián)查詢,多為字典表)或只分庫(kù)不分表且所有庫(kù)的表結(jié)構(gòu)完全一致的情況
actual-data-nodes: m1.course_$->{1..2}
#分片策略:?jiǎn)畏制I
table-strategy:
inline:
#分片鍵
sharding-column: id
#數(shù)據(jù)分片規(guī)則
algorithm-expression: course_$->{id % 2 + 1}
course_detail:
key-generator:
column: id
#主鍵生成策略 可選內(nèi)置的 SNOWFLAKE(雪花算法)/UUID
# 也可以自定義(實(shí)現(xiàn)ShardingKeyGenerator,并配置META-INF/services/org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator) SIMPLE
type: SNOWFLAKE
#配置 t_order 表規(guī)則 ->{a..b} a必須存在,否則分布式主鍵無(wú)法獲取
actual-data-nodes: m1.course_detail_$->{1..2}
table-strategy:
inline:
# 綁定表分片字段要一致(外鍵字段)
sharding-column: course_id
algorithm-expression: course_detail_$->{course_id % 2 + 1}
# 綁定表規(guī)則列表(避免查詢笛卡爾積),多套規(guī)則使用binding-tables[0],binding-tables[1]...
binding-tables: course_detail,course
注意:綁定表的分區(qū)鍵要一致
測(cè)試-綁定表查詢(笛卡爾積)
/**
* 綁定表測(cè)試(查詢笛卡爾積)
*/
@Test
public void bindingTest(){
List<Long> ids = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Course course = new Course();
course.setName("java" + i);
course.setStatus(1);
course.setCreateTime(new Date());
courseMapper.insert(course);
CourseDetail courseDetail = new CourseDetail();
courseDetail.setCourseId(course.getId());
courseDetail.setRemark("備注" + i);
courseDetailMapper.insert(courseDetail);
ids.add(course.getId());
}
List<Course> res = courseMapper.binding(ids);
log.info("查詢結(jié)果:{}",res.size());
}
首先注釋掉綁定表配置,查看關(guān)聯(lián)查詢笛卡爾積
# 綁定表規(guī)則列表(避免查詢笛卡爾積),多套規(guī)則使用binding-tables[0],binding-tables[1]...
#binding-tables: course_detail,course


然后打開綁定表配置,查看關(guān)聯(lián)查詢是否還有笛卡爾積
# 綁定表規(guī)則列表(避免查詢笛卡爾積),多套規(guī)則使用binding-tables[0],binding-tables[1]...
binding-tables: course_detail,course

3.9 分布式事務(wù)XA
默認(rèn)的 XA 事務(wù)管理器為 Atomikos
BASE事務(wù)管理器為Seata
配置事務(wù)管理器
package com.yss.datamiddle.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* @description:
* @author: Han LiDong
* @create: 2021/5/27 11:16
* @update: 2021/5/27 11:16
*/
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
@Bean
public PlatformTransactionManager txManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
使用分布式事務(wù)
@Test
// @Rollback(value = false)
@Transactional
@ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
public void transactionTest() {
Course course = new Course();
course.setName("java");
int rand = (int)(Math.random() * 10);
course.setStatus(rand % 2);
course.setCreateTime(new Date());
courseMapper.insert(course);
Course course1 = new Course();
course1.setName("java");
int rand1 = (int)(Math.random() * 10);
course1.setStatus(rand1 % 2 + 1);
course1.setCreateTime(new Date());
courseMapper.insert(course1);
int a = 1/0;
}
四、踩坑指南
4.1 項(xiàng)目引入shardingjdbc相關(guān)包,啟動(dòng)項(xiàng)目報(bào)錯(cuò)required a bean named ‘entityManagerFactory' that could not be found

解決方案:
注釋pom中durid、dynamic-datasource-spring-boot-starter引用
<!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter --> <!-- <dependency>--> <!-- <groupId>com.baomidou</groupId>--> <!-- <artifactId>dynamic-datasource-spring-boot-starter</artifactId>--> <!-- <version>3.1.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>com.alibaba</groupId>--> <!-- <artifactId>druid-spring-boot-starter</artifactId>--> <!-- </dependency>-->
4.2 項(xiàng)目啟動(dòng)報(bào)錯(cuò)
Failed to configure a DataSource: ‘url' attribute is not specified and no embedded datasource could be configured.
問(wèn)題描述:項(xiàng)目引入shardingjdbc包、配置好分片規(guī)則之后啟動(dòng)項(xiàng)目報(bào)錯(cuò)找不到數(shù)據(jù)庫(kù)配置,但是配置文件中明明按照sharding數(shù)據(jù)源配置規(guī)則配置了數(shù)據(jù)庫(kù)鏈接信息。
問(wèn)題原因:DruidDataSourceAutoConfigure在DynamciDataSourceAutoConfiguration之前,其會(huì)注入一個(gè)DataSourceWrapper,會(huì)在原生的spring.datasource下找url,username,password等。而我們動(dòng)態(tài)數(shù)據(jù)源的配置路徑是變化的。
解決方案二選一
springboot啟動(dòng)類增加如下配置
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})項(xiàng)目配置文件新增:
# 使用多數(shù)據(jù)源時(shí)要有這個(gè)配置,要不然會(huì)啟動(dòng)失敗。單數(shù)據(jù)源的時(shí)候不要加這個(gè)配置。 spring.autoconfigure.exclude = com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
4.3 項(xiàng)目啟動(dòng)報(bào)錯(cuò)
org.springframework.dao.InvalidDataAccessApiUsageException: ConnectionCallback; isValid; nested exception is java.sql.SQLFeatureNotSupportedException: isValid

解決方案:
此問(wèn)題是Spring Boot 2.3.8數(shù)據(jù)源健康檢查sql為null引起。
解決辦法是繼承 DataSourceHealthContributorAutoConfiguration 重寫 createIndicator 方法
package com.yss.datamiddle.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.Map;
/**
* @description: 重寫健康檢查sql,解決項(xiàng)目啟動(dòng)健康檢查異常
* @author: Han LiDong
* @create: 2021/5/28 14:40
* @update: 2021/5/28 14:40
*/
@Configuration
public class DataSourceHealthConfig extends DataSourceHealthContributorAutoConfiguration {
private static final String defaultQuery = "select 1";
public DataSourceHealthConfig(Map<String, DataSource> dataSources, ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
super(dataSources, metadataProviders);
}
@Override
protected AbstractHealthIndicator createIndicator(DataSource source) {
DataSourceHealthIndicator indicator = (DataSourceHealthIndicator) super.createIndicator(source);
if (!StringUtils.hasText(indicator.getQuery())) {
indicator.setQuery(defaultQuery);
}
return indicator;
}
}
4.4 分片表新增數(shù)據(jù),但是分片鍵未賦值導(dǎo)致全表入庫(kù)數(shù)據(jù)。
解決方法:分片鍵必須為非空,否則會(huì)全表新增數(shù)據(jù)。
4.5 項(xiàng)目啟動(dòng)報(bào)錯(cuò)
Caused by: org.hibernate.AnnotationException: No identifier specified for entity: com.yss.datamiddle.po.PrometheusAlertRecordSummaryPo
解決方法:ORM實(shí)體類必須有主鍵注解 @Id
4.6 Table ‘xxx_sequences' doesn't exist

解決方案:分表字段主鍵生成策略改為:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
4.7 Table ‘tablename' doesn't exist
分片表對(duì)應(yīng)ORM映射實(shí)體類 去掉表名映射:@TableName(“source”)

4.8 org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException: Configuration property name ‘spring.shardingsphere.datasource.monitor_1' is not valid
解決方案:yml配置key不能包含下劃線,調(diào)整monitor_1為monitor-1
4.9 報(bào)錯(cuò):
Caused by: java.lang.NullPointerException: please config application id within seata.conf file.
原因:使用XA分布式事務(wù),但同時(shí)又引入了Base事務(wù)相關(guān)包
解決方案:注釋掉Base事務(wù)包
<!-- 使用 BASE 事務(wù)時(shí),需要引入此模塊 --> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>sharding-transaction-base-seata-at</artifactId>--> <!-- <version>4.1.1</version>--> <!-- </dependency>--> <!-- <!– https://mvnrepository.com/artifact/io.seata/seata-core –>--> <!-- <dependency>--> <!-- <groupId>io.seata</groupId>--> <!-- <artifactId>seata-core</artifactId>--> <!-- <version>1.4.2</version>--> <!-- </dependency>-->
以上就是springboot整合shardingjdbc實(shí)現(xiàn)分庫(kù)分表最簡(jiǎn)單demo的詳細(xì)內(nèi)容,更多關(guān)于springboot整合shardingjdbc分庫(kù)分表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot+MybatisPlus實(shí)現(xiàn)sharding-jdbc分庫(kù)分表的示例代碼
- SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分庫(kù)分表
- SpringBoot?如何使用sharding?jdbc進(jìn)行分庫(kù)分表
- SpringBoot整合sharding-jdbc實(shí)現(xiàn)自定義分庫(kù)分表的實(shí)踐
- SpringBoot整合sharding-jdbc實(shí)現(xiàn)分庫(kù)分表與讀寫分離的示例
- SpringBoot 2.0 整合sharding-jdbc中間件實(shí)現(xiàn)數(shù)據(jù)分庫(kù)分表
- SpringBoot集成Sharding-JDBC實(shí)現(xiàn)分庫(kù)分表方式
相關(guān)文章
java對(duì)xml節(jié)點(diǎn)屬性的增刪改查實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇java對(duì)xml節(jié)點(diǎn)屬性的增刪改查實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
springboot加載復(fù)雜的yml文件獲取不到值的解決方案
這篇文章主要介紹了springboot加載復(fù)雜的yml文件獲取不到值的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringBoot @value注解動(dòng)態(tài)刷新問(wèn)題小結(jié)
@Value注解 所對(duì)應(yīng)的數(shù)據(jù)源來(lái)自項(xiàng)目的 Environment 中,我們可以將數(shù)據(jù)庫(kù)或其他文件中的數(shù)據(jù),加載到項(xiàng)目的 Environment 中,然后 @Value注解 就可以動(dòng)態(tài)獲取到配置信息了,這篇文章主要介紹了SpringBoot @value注解動(dòng)態(tài)刷新,需要的朋友可以參考下2023-09-09
idea創(chuàng)建springboot項(xiàng)目和springcloud項(xiàng)目的詳細(xì)教程
這篇文章主要介紹了idea創(chuàng)建springboot項(xiàng)目和springcloud項(xiàng)目方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
Java開發(fā)之內(nèi)部類對(duì)象的創(chuàng)建及hook機(jī)制分析
這篇文章主要介紹了Java開發(fā)之內(nèi)部類對(duì)象的創(chuàng)建及hook機(jī)制,結(jié)合實(shí)例形式分析了java基于hook機(jī)制內(nèi)部類對(duì)象的創(chuàng)建與使用,需要的朋友可以參考下2018-01-01
基于java.lang.IllegalArgumentException異常報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了基于java.lang.IllegalArgumentException異常報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Spring基于注解管理bean實(shí)現(xiàn)方式講解
很多時(shí)候我們需要根據(jù)不同的條件在容器中加載不同的Bean,或者根據(jù)不同的條件來(lái)選擇是否在容器中加載某個(gè)Bean,這就是Bean的加載控制,一般我們可以通過(guò)編程式或注解式兩種不同的方式來(lái)完成Bean的管理2023-01-01
img 加載網(wǎng)絡(luò)圖片失敗 顯示默認(rèn)圖片的方法
下面小編就為大家?guī)?lái)一篇img 加載網(wǎng)絡(luò)圖片失敗 顯示默認(rèn)圖片的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05

