Site officiel de Shardingsphere : https://shardingsphere.apache.org/
Scénario : Si les données de la table sont trop volumineuses, nous devrons peut-être diviser une table en plusieurs tables. Ici, la fonction de fractionnement de table est implémentée via ShardingSphere, mais pas la base de données.
sphère de partage
Utilisez rapidement shardingsphere pour le partitionnement de table
1. Conception de la base de données
J'ai construit ici une bibliothèque appelée myqxin pour démontrer cette opération de fractionnement de table. Il existe sx_text_a comme table de base, qui est divisée en trois tables pour le stockage. Les sous-tables sont sx_test_a0, sx_test_a1, sx_test_a2. Les structures des tables sont les mêmes, mais les noms des tables sont différents.
Comme le montre l'image :
sx_test_a1 et sx_test_a2 sont les mêmes que ci-dessus, donc aucune capture d'écran n'est affichée.
2. Le projet Springboot intègre shardingsphere
Structure du projet si :
Il s'agit d'un simple projet Springboot, principalement destiné à démontrer l'opération de fractionnement de table.
- Dépendances du projet
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myqxin</groupId>
<artifactId>itcast-sharding-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis-plus begin-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<!--MySQL连接包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!--工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
<!-- 本次演示必须的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
</dependencies>
</project>
La plupart d'entre elles sont des dépendances de base requises par le projet. Les deux dernières dépendances sont nécessaires à cette démonstration.
- fichier de configuration application.properties
server.port=9018
# 数据源 主库
spring.shardingsphere.datasource.names=master
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://localhost:3306/myqxin?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=root
# 数据分表规则
# 指定所需分的表
spring.shardingsphere.sharding.tables.sx_test_a.actual-data-nodes=master.sx_test_a$->{
0..2}
# 指定字段
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.inline.sharding-column=indexes
# 分表规则为该字段值除以3取模
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.inline.algorithm-expression=sx_test_a$->{
indexes % 3}
# 打印SQL
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
Ici, nous effectuons uniquement des opérations de fractionnement de tables sur une seule base de données, il n'est donc pas nécessaire d'avoir plusieurs sources de données. Je n'expliquerai pas les informations de connexion de données.
Expliquez ce qui suit :
spring.shardingsphere.sharding.tables.sx_test_a.actual-data-nodes=master.sx_test_a$->{0..2}
# 指定字段
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.inline.sharding-column=indexes
# 分表规则为该字段值除以3取模
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.inline.algorithm-expression=sx_test_a$->{indexes % 3}
sx_test_a:这个是基础表,这没什么好说的
master.sx_test_a$->{
0..2}:
master这个主数据源节点,这个很好理解。
sx_test_a基础表也不说了。
$->{
0..2}如下图介绍(看完下图,说一下我理解配置与数据库之间的关系:因为$->{
0..2}是区间,由此它的内容加上前面的sx_test_a就组合成了sx_test_a0、sx_test_a1、sx_test_a2。这与我们库里面所分的三个表是一致,这样他们就能通过这个匹配上(根据inline.algorithm-expression=sx_test_a$->{
indexes % 3}规则),将数据插入到匹配对应的表)
indexes:这个理解为你库里面的某个字段
$->{
indexes % 3}如下图介绍(看完下图,说一下我的理解,我们可以把上面的actual-data-nodes=master.sx_test_a$->{
0..2}这个看成一个整体,就是我们总共要插入的表,而$->{
indexes % 3}这个拿着indexes字段的值跟3取模,得到的结果就是我们从整体操作的表中选择指定的一表进行数据的插入)
- SxTestAController.java
package com.myqxin.controller;
import com.myqxin.pojo.SxTestA;
import com.myqxin.service.SxTestAService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author: myqxin
* @Desc:
* @create: 2021-12-21 09:22
**/
@RestController
@RequestMapping("/sxa")
public class SxTestAController {
@Autowired
private SxTestAService sxTestAService;
@GetMapping("/savetest")
public String saveTest(){
ArrayList<SxTestA> sxTestAS = new ArrayList<>();
sxTestAS.add(new SxTestA(1,"number1",1,"2020","no","1234.3"));
sxTestAS.add(new SxTestA(2,"number2",2,"2020","no","14.3"));
sxTestAS.add(new SxTestA(3,"number3",3,"2020","no","5434.3"));
sxTestAS.add(new SxTestA(4,"number4",4,"2020","no","334.3"));
sxTestAS.add(new SxTestA(5,"number5",5,"2020","no","134.3"));
sxTestAService.saveBatch(sxTestAS);
return "myqxin";
}
@GetMapping("/list")
public ResponseEntity<List<SxTestA>> list(){
return ResponseEntity.ok(sxTestAService.list());
}
}
3. Demandez un test
- Envoyer la demande : localhost:9018/sxa/savetest
Vous pouvez voir l'instruction SQL print depuis la console. Il y a trop de contenu et seule une partie a été interceptée, comme le montre la figure :
Regardons l'effet de la base de données :
- sx_test_a0
- sx_test_a1
- sx_test_a2
Ceci termine l'insertion des données dans le sous-tableau.
Explication détaillée:
4. Obtenez toutes les données que vous venez d'insérer
- Envoyer la demande : localhost:9018/sxa/list
Le résultat de la requête est tel qu'indiqué dans la figure :
On peut voir que même si nous avons divisé les tables, cela nous aide toujours à les regrouper lors de l'interrogation de toutes les tables.
Personnalisez les règles de fractionnement des tables pour mettre en œuvre le fractionnement des tables en fonction de la date et du mois.
Le projet est toujours ce projet, mais le fichier de configuration doit apporter quelques modifications. Règles : Sur la table de base sx_test_a, utilisez l'heure du champ data_time pour diviser les tables en mois. Autrement dit, dans la bibliothèque myqxin, il y a un total de 12 tables de sx_test_a2021_1 à sx_test_a2021_12, car il y a trop de tables. Oui, je ne prends que quatre mois de données et je ne crée que 4 tables. Lorsque la table est créée, la table peut être générée dynamiquement via une procédure stockée.
- Le tableau est le suivant :
1. fichier application.properties
server.port=9018
# 数据源 主库
spring.shardingsphere.datasource.names=master
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://localhost:3306/myqxin?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=root
# 指定所需分的表
spring.shardingsphere.sharding.tables.sx_test_a.actual-data-nodes=master.sx_test_a$->{2021}_$->{1..4}
# 指定字段
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.standard.sharding-column=date_time
# 自定义分表规则
spring.shardingsphere.sharding.tables.sx_test_a.table-strategy.standard.precise-algorithm-class-name=com.myqxin.utils.TableShardingAlgor
# 打印SQL
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
2. Classe d'implémentation personnalisée TableShardingAlgor.java
package com.myqxin.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.logging.SimpleFormatter;
/**
* @author: myqxin
* @Desc:
* @create: 2021-12-22 09:41
**/
@Slf4j
public class TableShardingAlgor implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
// 真实节点
collection.stream().forEach((item) -> {
log.info("actual node table:{}",item);
});
// 精确分片
log.info("column value:{}",preciseShardingValue.getValue());
// 获取基础表名
String tb_name = preciseShardingValue.getLogicTableName();
// 根据当前日期来分表
String format = null;
try {
// 这里的类型是根据你实现PreciseShardingAlgorithm的类型String
String date = preciseShardingValue.getValue();
// 我这里需要进行一些处理(不需要的可以忽略)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M");
Date parse = sdf.parse(date);
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy_M");
format = sdf1.format(parse);
} catch (ParseException e) {
e.printStackTrace();
}
// 选择表 基础表+分表名称
tb_name = tb_name + format;
System.out.println("tb_name: "+tb_name);
// 进行匹配
for (String each : collection) {
System.out.println("sx_text_a: "+each);
if (each.equals(tb_name)){
// 匹配成功开始对这个表进行数据的插入
return each;
}
}
throw new IllegalArgumentException();
}
}
- SxTestAController.java
package com.myqxin.controller;
import com.myqxin.pojo.SxTestA;
import com.myqxin.service.SxTestAService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author: myqxin
* @Desc:
* @create: 2021-12-21 09:22
**/
@RestController
@RequestMapping("/sxa")
public class SxTestAController {
@Autowired
private SxTestAService sxTestAService;
@GetMapping(value = "/readDirectory")
public String readDirectory(){
sxTestAService.readDirectory();
return "myqxin";
}
}
- SxTestAServiceImpl.java
package com.myqxin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myqxin.mapper.SxTestAMapper;
import com.myqxin.pojo.SxTestA;
import com.myqxin.service.SxTestAService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author: myqxin
* @Desc:
* @create: 2021-12-21 09:21
**/
@Service
public class SxTestAServiceImpl extends ServiceImpl<SxTestAMapper, SxTestA> implements SxTestAService {
@Autowired(required = false)
private SxTestAMapper sxTestAMapper;
@Override
public void readDirectory() {
String path = "C:\\Users\\Administrator\\Desktop\\pams";
File file = new File(path);
if (!file.isDirectory()) {
System.err.println(file.getPath() + ": " + file.getName());
} else if (file.isDirectory()) {
String[] list = file.list();
for (int i = 0; i < list.length; i++) {
ArrayList<SxTestA> sxMonitors = new ArrayList<>();
File file1 = new File(path + "\\" + list[i]);
try {
String s = FileUtils.readFileToString(new File(file1.getPath()), "utf-8");
String[] lonList = s.split("\r|\n|\\s+");
for (int i1 = 0; i1 < lonList.length; i1++) {
SxTestA sxMonitor = new SxTestA();
String[] split = file1.getName().split("\\_|\\.");
sxMonitor.setNumber(file1.getName());
sxMonitor.setIndexes(i1);
String taskTime = split[1];
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HH");
Date date = sdf.parse(taskTime);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String sr = df.format(date);
sxMonitor.setDateTime(sr);
sxMonitor.setMonitorName(split[0]);
sxMonitor.setMonitorValue(lonList[i1]);
sxMonitors.add(sxMonitor);
}
System.err.println(sxMonitors.size()); //13345
// TODO 数据批处理
saveBatch(sxMonitors);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public List<SxTestA> selectAll() {
QueryWrapper<SxTestA> wrapper = new QueryWrapper<>();
//wrapper.eq("date_time",timeStamp);
return list();
}
}
J'ai créé les données moi-même. Vous pouvez créer des données pour la simulation en fonction des tableaux que vous avez créés.
3. Demandez un test
- Envoyer la demande : localhost:9018/sxa/readDirectory
L'effet de la base de données est le suivant :
sx_test_a2021_1
sx_test_a2021_2
sx_test_a2021_3
Je n’en montrerai ici que trois. L’effet ici a été atteint.
Questions fréquemment posées et solutions
- Message d'erreur 1 :
La raison en est que lorsque nous utilisons le framework shardingsphere pour le partitionnement de table,
spring.main.allow-bean-definition-overriding=true n'est pas ajouté au fichier de configuration.
spring.main.allow-bean-definition-overriding=true
Cela peut être résolu en l'ajoutant à notre fichier de configuration
- Message d'erreur 2 :
La raison est que la version de mysql que vous utilisez actuellement n'est pas cohérente avec la version qui dépend de l'importation. Modifiez-la simplement pour qu'elle soit cohérente, car j'ai installé la version mysql8.0.27.
Par conséquent, la dépendance de connexion à la base de données introduite est 8.0.27.
<!--MySQL连接包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
Le projet que je démontre cette fois utilise la version 8.0.13, donc les dépendances introduites sont également la 8.0.13. Vous pouvez les modifier en fonction de la version réelle utilisée.
Créer dynamiquement des tables de base de données
Dans ce cas, nous les avons créés une à une sur les tables de base. Cela ne répond pas aux besoins réels de l'entreprise. Nous devons confier l'opération de création des tables au programme et générer dynamiquement les tables souhaitées (il est recommandé d'écrire un tâche planifiée pour exécuter ce programme de création de table). J'ai réglé le problème. Cliquez pour le visualiser. Créez dynamiquement des tables de base de données.