sharding-jdbc屬於ShardingSphere的一員,定位為輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數據庫,以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。
- 適用於任何基於Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基於任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意實現JDBC規范的數據庫。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
sharding-jdbc數據分片工作原理
核心由 SQL解析 => 執行器優化=> SQL路由 => SQL改寫 => SQL執行 => 結果歸並 的流程組成。
sql解析
分為詞法解析和語法解析。 先通過詞法解析器將SQL拆分為一個個不可再分的單詞。再使用語
法解析器對SQL進行理解,並最終提煉出解析上下文。 解析上下文包括表、選擇項、排序項、
分組項、聚合函數、分頁信息、查詢條件以及可能需要修改的占位符的標記。
執行器優化
合並和優化分片條件,如OR等
sql路由
根據解析上下文匹配用戶配置的分片策略,並生成路由路徑。目前支持分片路由和廣播路由。
sql改寫
將SQL改寫為在真實數據庫中可以正確執行的語句。SQL改寫分為正確性改寫和優化改寫
sql執行
通過多線程執行器異步執行
結果歸並
將多個執行結果集歸並以便於通過統一的JDBC接口輸出。結果歸並包括流式歸並、內存歸並和
使用裝飾者模式的追加歸並這幾種方式
sharding-jdbc讀寫分離
springboot2.x + mybatis + sharding-jdbc
手動配置方式
maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
配置數據源
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.maxActive:50}")
private int maxActive;
@Value("${spring.datasource.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.idleTimeout}")
private int idleTimeout;
@Value("${spring.datasource0.url}")
private String url0;
@Value("${spring.datasource0.username}")
private String username0;
@Value("${spring.datasource0.password}")
private String password0;
@Value("${spring.datasource0.driverClassName}")
private String driverClassName0;
@Value("${spring.datasource1.url}")
private String url1;
@Value("${spring.datasource1.username}")
private String username1;
@Value("${spring.datasource1.password}")
private String password1;
@Value("${spring.datasource1.driverClassName}")
private String driverClassName1;
@Autowired
private CustomerDataSourceFactory customerDataSourceFactory;
@Bean("dataSource0")
public DataSource dataSource0() {
return initDataSource(url0,username0,password0,driverClassName0);
}
@Bean("dataSource1")
public DataSource dataSource1() {
return initDataSource(url1,username1,password1,driverClassName1);
}
@Bean(name = "shardingDataSource")
public DataSource shardingDataSource(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1) throws SQLException {
Map<String,DataSource> map= new HashMap<>();
map.put("ds_master",dataSource0);
map.put("ds_slave",dataSource1);
return customerDataSourceFactory.createDataSource(map);
}
private DataSource initDataSource(String url,String username,String password, String driverClassName) {
HikariDataSource datasource = new HikariDataSource();
datasource.setJdbcUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setMaximumPoolSize(maxActive);
datasource.setMinimumIdle(minIdle);
datasource.setMaxLifetime(maxLifetime);
datasource.setIdleTimeout(idleTimeout);
datasource.setConnectionTestQuery("select 1");
return datasource;
}
}
@Component
public class CustomerDataSourceFactory {
public DataSource createDataSource(Map<String,DataSource> map ) throws SQLException {
MasterSlaveRuleConfiguration masterSlaveRuleConfiguration = new MasterSlaveRuleConfiguration("ds_master_slave","ds_master", Lists.newArrayList("ds_slave"));
Properties properties = new Properties();
properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(),
String.valueOf(true));
properties.setProperty(ShardingPropertiesConstant.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(),
String.valueOf(200));
return MasterSlaveDataSourceFactory.createDataSource(map, masterSlaveRuleConfiguration,
properties);
}
}
mybatis配置
@Configuration
@MapperScan(basePackages = "com.example.dao" ,sqlSessionFactoryRef="shardingSqlSessionFactory" )
@EnableTransactionManagement
public class ShardingMyBatisConfig {
@Autowired
@Qualifier("shardingDataSource")
private DataSource dataSource;
@Bean("shardingSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.example.entity");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
return bean.getObject();
}
@Bean("platformTransactionManager")
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
springboot-starter集成
maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
application.yml配置
server:
port: 8085
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3339/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
ds1:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3340/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
masterslave:
load-balance-algorithm-type: round_robin
name: ms
master-data-source-name: ds0
slave-data-source-names: ds1
props:
sql.show: true
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
啟動類上加上mapper掃描路徑及開啟事務
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.example.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在例子中我們使用了mybatis-starter,所以無需配SqlSessionFactory等,如果手動整合則直接通過注入的方式即可使用DataSource,或者將DataSource配置在JPA、Hibernate或MyBatis中使用。
@Resource
private DataSource dataSource;
讀寫分離配置項說明
masterSlaveRule:
name: #讀寫分離數據源名稱,自定義
masterDataSourceName: #主庫數據源名稱,數據源處定義的數據源名
slaveDataSourceNames: #從庫數據源名稱列表,數據源處定義的數據源名
- <data_source_name1>
- <data_source_name2>
- <data_source_name_x>
loadBalanceAlgorithmClassName: #從庫負載均衡算法類名稱。該類需實現MasterSlaveLoadBalanceAlgorithm接口且提供無參數構造器
loadBalanceAlgorithmType: #從庫負載均衡算法類型,可選值:ROUND_ROBIN,RANDOM。`loadBalanceAlgorithmClassName`存在則忽略該配置
sharding-jdbc 可配置項參數說明
props:
sql.show: #是否開啟SQL顯示,默認值: false
acceptor.size: # accept連接的線程數量,默認為cpu核數2倍
executor.size: #工作線程數量最大,默認值: 無限制
max.connections.size.per.query: # 每個查詢可以打開的最大連接數量,默認為1
check.table.metadata.enabled: #是否在啟動時檢查分表元數據一致性,默認值: false
proxy.frontend.flush.threshold: # proxy的服務時候,對於單個大查詢,每多少個網絡包返回一次
proxy.transaction.type: # 默認LOCAL,proxy的事務模型 允許LOCAL,XA,BASE三個值,LOCAL無分布式事務,XA則是采用atomikos實現的分布式事務 BASE目前尚未實現
proxy.opentracing.enabled: # 是否啟用opentracing
proxy.backend.use.nio: # 是否采用netty的NIO機制連接后端數據庫,默認False ,使用epoll機制
proxy.backend.max.connections: # 使用NIO而非epoll的話,proxy后台連接每個netty客戶端允許的最大連接數量(注意不是數據庫連接限制) 默認為8
proxy.backend.connection.timeout.seconds: #使用nio而非epoll的話,proxy后台連接的超時時間,默認60s
sharind-jdbc分庫分表
分片維度配置
對於分片策略存有數據源分片策略和表分片策略兩種維度。兩種策略的API完全相同。
- 數據源分片策略
對應於DatabaseShardingStrategy。用於配置數據被分配的目標數據源。 - 表分片策略
對應於TableShardingStrategy。用於配置數據被分配的目標表,該目標表存在與該數據的目標數據源內。故表分片策略是依賴與數據源分片策略的結果的。
分片策略
包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。
- 標准分片策略
對應StandardShardingStrategy。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND分片,如果不配置
RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理。 - 復合分片策略
對應ComplexShardingStrategy。復合分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關系復雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。 - 行表達式分片策略
對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: t_user_$->{u_id % 8} 表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0 到 t_user_7 。 - Hint分片策略
對應HintShardingStrategy。通過Hint而非SQL解析的方式分片的策略。 - 不分片策略
對應NoneShardingStrategy。不分片的策略。
行表達式語法說明
行表達式的使用非常直觀,只需要在配置中使用 ${ expression } 或 $->{ expression } 標識行表達式即可。 目前支持數據節點和分片算法這兩個部分的配置。行表達式的內容使用的是Groovy的語法,Groovy能夠支持的所有操作,行表達式均能夠支持。例如:
- ${begin..end} 表示范圍區間
- ${[unit1, unit2, unit_x]} 表示枚舉值
- 行表達式中如果出現連續多個 ${ expression } 或 $->{ expression } 表達式,整個表達式最終的結果將會根據每個子表達式的結果進行笛卡爾組合。
分布式主鍵
ShardingSphere不僅提供了內置的分布式主鍵生成器,例如UUID、SNOWFLAKE、LEAF(進行中),還抽離出分布式主鍵生成器的接口,方便用戶自行實現自定義的自增主鍵生成器。
配置項說明
defaultKeyGenerator: #默認的主鍵生成算法 如果沒有設置,默認為SNOWFLAKE算法
column: # 自增鍵對應的列名稱
type: #自增鍵的類型,主要用於調用內置的主鍵生成算法有三個可用值:SNOWFLAKE(時間戳+worker id+自增id),UUID(java.util.UUID類生成的隨機UUID),LEAF,其中Snowflake算法與UUID算法已經實現,LEAF目前尚未實現
props:
# 定制算法需要設置的參數,比如SNOWFLAKE算法的worker.id與max.tolerate.time.difference.milliseconds
自定義主鍵生成策略
第一步:添加策略類
public class CustomIdGenerator implements ShardingKeyGenerator {
@Override
public Comparable<?> generateKey() {
return UUID.randomUUID().toString().replace("-","");
}
@Override
public String getType() {
return "CUSTOM";
}
@Override
public Properties getProperties() {
return null;
}
@Override
public void setProperties(Properties properties) {
}
}
第二步:配置spi
第三步:通過手動配置或者在yml文件中配置使用
綁定表
指分片規則一致的主表和子表。例如: t_order 表和 t_order_item 表,均按照 order_id 分片,則此兩張表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。
配置示例
bindingTables: # 綁定表,也就是實際上哪些配置的sharidng表規則需要實際生效的列表,配置為yaml列表,並且允許單個條目中以逗號切割,所配置表必須已經配置為邏輯表
- sharding_t1
- sharding_t2,sharding_t3
廣播表
指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表
配置實例
broadcastTables: # 廣播表 這里配置的表列表,對於發生的所有數據變更,都會不經sharidng處理,而是直接發送到所有數據節點,注意此處為列表,每個項目為一個表名稱
- broad_1
- broad_2
手動配置方式
數據源配置
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.maxActive:50}")
private int maxActive;
@Value("${spring.datasource.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.idleTimeout}")
private int idleTimeout;
@Value("${spring.datasource0.url}")
private String url0;
@Value("${spring.datasource0.username}")
private String username0;
@Value("${spring.datasource0.password}")
private String password0;
@Value("${spring.datasource0.driverClassName}")
private String driverClassName0;
@Value("${spring.datasource1.url}")
private String url1;
@Value("${spring.datasource1.username}")
private String username1;
@Value("${spring.datasource1.password}")
private String password1;
@Value("${spring.datasource1.driverClassName}")
private String driverClassName1;
@Autowired
private CustomerDataSourceFactory customerDataSourceFactory;
@Bean("dataSource0")
public DataSource dataSource0() {
return initDataSource(url0,username0,password0,driverClassName0);
}
@Bean("dataSource1")
public DataSource dataSource1() {
return initDataSource(url1,username1,password1,driverClassName1);
}
@Bean(name = "shardingDataSource")
public DataSource shardingDataSource(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1) throws SQLException {
List<DataSource> dataSourceList = new ArrayList<>();
dataSourceList.add(dataSource0);
dataSourceList.add(dataSource1);
return customerDataSourceFactory.createDataSource(dataSourceList);
}
private DataSource initDataSource(String url,String username,String password, String driverClassName) {
HikariDataSource datasource = new HikariDataSource();
datasource.setJdbcUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setMaximumPoolSize(maxActive);
datasource.setMinimumIdle(minIdle);
datasource.setMaxLifetime(maxLifetime);
datasource.setIdleTimeout(idleTimeout);
datasource.setConnectionTestQuery("select 1");
return datasource;
}
}
分庫分表規則配置
@Component
public class CustomerDataSourceFactory {
public DataSource createDataSource(List<DataSource> dataSourceList) throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
/**user表分表配置**/
TableRuleConfiguration userTableRuleConfig = new TableRuleConfiguration("t_user","db0.t_user_0,db0.t_user_1");
userTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("CUSTOM","user_id"));
userTableRuleConfig.setDatabaseShardingStrategyConfig(new NoneShardingStrategyConfiguration());
userTableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_id",new UserTableAlgoritm()));
shardingRuleConfig.getTableRuleConfigs().add(userTableRuleConfig);
/**order表分庫分表配置**/
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration(
"t_order", "db0.t_order_2020_0,db0.t_order_2020_1,db0.t_order_2019_0,db0.t_order_2019_1,db1.t_order_2019_0,db1.t_order_2019_1,db1.t_order_2020_0,db1.t_order_2020_1");
orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE","order_id"));
// 數據庫分片配置
StandardShardingStrategyConfiguration dataSourceStrategyConfiguration = new StandardShardingStrategyConfiguration(
"addr", new OrderDataSourcePreciseShardingAlgorithm());
orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataSourceStrategyConfiguration);
orderTableRuleConfig.setTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration(
"order_year,user_id", new OrderTableComplexKeyAlgorithm()));
shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
Properties properties = new Properties();
properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(),
String.valueOf(true));
properties.setProperty(ShardingPropertiesConstant.MAX_CONNECTIONS_SIZE_PER_QUERY.getKey(),
String.valueOf(200));
Map<String, DataSource> dataSourceMap = new HashMap<>();
for (int i = 0; i < dataSourceList.size(); i++) {
dataSourceMap.put("db" + String.valueOf(i), dataSourceList.get(i));
}
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig,
properties);
}
}
這里分別對user和order進行可分庫分表,其中user單分片鍵只分表不分庫,order表分庫采用復合分片策略,分表采用單分片鍵。
對應的分庫分表策略如下
@Slf4j
public class UserTableAlgoritm implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
log.info("availableTargetNames={},shardingValue={}", ObjectToStrUtils.JSONString(availableTargetNames),ObjectToStrUtils.JSONString(shardingValue));
String value = shardingValue.getValue();
List<String> list = Lists.newArrayList(availableTargetNames);
return list.get(value.hashCode()%list.size());
}
}
@Slf4j
public class OrderTableComplexKeyAlgorithm implements ComplexKeysShardingAlgorithm<String> {
public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
List<String> list = new ArrayList<>();
log.info("availableTargetNames={},shardingValue={}",availableTargetNames,shardingValue);
Map<String, Collection<String>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
if(columnNameAndShardingValuesMap.containsKey("order_year")){
Collection<String> orderYear = columnNameAndShardingValuesMap.get("order_year");
String address = orderYear.iterator().next();
list = availableTargetNames.stream().filter(t -> t.contains(address)).collect(Collectors.toList());
}
if(columnNameAndShardingValuesMap.containsKey("user_id")){
Collection<String> userId = columnNameAndShardingValuesMap.get("user_id");
String stringUserId = userId.iterator().next();
String index = String.valueOf(stringUserId.hashCode()%list.size());
list = list.stream().filter(t -> t.endsWith(index)).collect(Collectors.toList());
}
log.info("actual table :{}",list);
return list;
}
}
@Slf4j
public class OrderDataSourcePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
public String doSharding(Collection<String> availableDataSourceNames, PreciseShardingValue<String> shardingValue) {
log.info("availableDataSourceNames={},shardingValue={}",availableDataSourceNames,shardingValue);
String value = shardingValue.getValue();
Iterator<String> iterator = availableDataSourceNames.iterator();
List<String> list = Lists.newArrayList(availableDataSourceNames);
int index = value.hashCode()%list.size();
return list.get(index);
}
}
springboot-starter配置讀寫分離+分庫分表
spring:
shardingsphere:
datasource:
names: db0_master,db0_slave,db1_master,db1_slave
db0_master:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3339/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db0_slave:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3341/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db1_master:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3340/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
db1_slave:
type: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3342/sharding01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.cj.jdbc.Driver
sharding:
tables:
t_order:
actual-data-nodes: db0.t_order_2020_0,db0.t_order_2020_1,db0.t_order_2019_0,db0.t_order_2019_1,db1.t_order_2019_0,db1.t_order_2019_1,db1.t_order_2020_0,db1.t_order_2020_1
database-strategy:
standard:
shardingColumn: addr
preciseAlgorithmClassName: com.example.config.OrderDataSourcePreciseShardingAlgorithm
table-strategy:
complex:
shardingColumns: order_year,user_id
algorithmClassName: com.example.config.OrderTableComplexKeyAlgorithm
keyGenerator:
column: order_id
master-slave-rules:
db0:
master-data-source-name: db0_master
slave-data-source-names: db0_slave
loadBalanceAlgorithmType: ROUND_ROBIN
db1:
master-data-source-name: db1_master
slave-data-source-names: db1_slave
loadBalanceAlgorithmType: ROUND_ROBIN
props:
sql.show: true
default-key-generator:
type: SNOWFLAKE
props:
work.id: 100
需要注意的是分片時的數據源是讀寫分離中定義的集群名。
參考文檔
官方文檔:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/
源碼地址:https://github.com/apache/shardingsphere/tree/4.1.0/examples/sharding-jdbc-example/sharding-example