欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot+shardingsphere實現(xiàn)按月分表功能教程

 更新時間:2025年04月21日 10:42:48   作者:無名指的等待712  
這篇文章主要介紹了SpringBoot+shardingsphere實現(xiàn)按月分表功能教程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

ShardingSphere 是一套開源的分布式數(shù)據(jù)庫中間件解決方案,旨在簡化數(shù)據(jù)庫分片、讀寫分離、分布式事務(wù)等復(fù)雜場景的管理。

它由 Apache 軟件基金會支持,廣泛應(yīng)用于需要處理大規(guī)模數(shù)據(jù)的系統(tǒng)中

一、ShardingSphere 是什么?

主要是為了防止一張表的數(shù)據(jù)量過大而設(shè)計的,數(shù)據(jù)庫本身就支持,但是由于自行設(shè)計需要滿足跨表查詢,事務(wù)一致性,分頁聚合等很多的復(fù)雜場景,還需要很多的配套監(jiān)控,設(shè)計,擴(kuò)容等方案,所以總體來說是一個任務(wù)量很大的任務(wù),故而這里采用ShardingSphere 來實現(xiàn)。

二、使用步驟

1.引入庫

<!-- 分庫分表 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.2.0</version>
        </dependency>

2.環(huán)境配置+Mysql表

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `gender` tinyint(4) NOT NULL COMMENT '0:男 1:女',
  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1890651990057906179 DEFAULT CHARSET=utf8mb4;
# 配置服務(wù)器端口
server:
  port: 9999

# Spring框架下的ShardingSphere配置
spring:
  shardingsphere:
    # 模式配置,設(shè)置為獨立模式
    mode:
      type: Standalone
    # 數(shù)據(jù)源配置
    datasource:
      # 定義數(shù)據(jù)源名稱
      names: ds0
      # 數(shù)據(jù)源ds0的具體配置
      ds0:
        # 數(shù)據(jù)源類型為HikariCP
        type: com.zaxxer.hikari.HikariDataSource
        # 數(shù)據(jù)庫驅(qū)動類名稱
        driver-class-name: com.mysql.cj.jdbc.Driver
        # 數(shù)據(jù)庫連接URL,包含時區(qū)設(shè)置
        jdbc-url: jdbc:mysql://localhost:3306/sharding_db?serverTimezone=Asia/Shanghai
        # 數(shù)據(jù)庫用戶名
        username: root
        # 數(shù)據(jù)庫密碼
        password: root
    # 規(guī)則配置
    rules:
      # 分片規(guī)則配置
      sharding:
        # 定義分片的表
        tables:
          user:
            # 只配置基礎(chǔ)表,其他表會動態(tài)創(chuàng)建
            actual-data-nodes: ds0.user,ds0.user_202401,ds0.user_202402,ds0.user_202403,ds0.user_202404,ds0.user_202405
            table-strategy:
              standard:
                sharding-column: createtime
                sharding-algorithm-name: user_inline
            # 添加主鍵生成策略
            key-generate-strategy:
              column: id
              key-generator-name: snowflake
        sharding-algorithms:
          user_inline:
            type: CLASS_BASED
            props:
              strategy: standard
              algorithmClassName: com.hhh.sharding.standa.UserShardingAlgorithm
        # 配置主鍵生成器
        key-generators:
          snowflake:
            type: SNOWFLAKE
            props:
              worker-id: 123
        # 添加默認(rèn)分片策略
        default-sharding-column: gender
    # 屬性配置
    props:
      # 是否顯示SQL語句
      sql-show: true

# MyBatis-Plus配置
mybatis-plus:
  configuration:
    # 不將下劃線轉(zhuǎn)換為駝峰命名
    map-underscore-to-camel-case: false
    # 使用標(biāo)準(zhǔn)輸出日志實現(xiàn)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    enable-sql-runner: true

這里有一個注意事項,那就是id一定要使用bigint使用雪花策略算法來實現(xiàn),至于為什么這樣呢,是為了防止分表的主鍵id一致的情況,這里首先推薦就是使用mybatisPlus來實現(xiàn),因為他天然支持雪花算法

3.分表代碼實現(xiàn)

主要是兩個文件一個是自己實現(xiàn)分表算法的UserShardingAlgorithm文件

package com.hhh.sharding.standa;

import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
import com.hhh.sharding.domain.User;
import com.hhh.sharding.service.UserService;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.rule.ShardingRule;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class DynamicShardingManager {

    @Resource
    private DataSource dataSource;

    @Resource
    private UserService userService;


    private static final String LOGIC_TABLE_NAME = "user";

    private static final String DATABASE_NAME = "sharding_db"; // 配置文件中的數(shù)據(jù)庫名稱

    @PostConstruct
    public void initialize() {
        log.info("初始化動態(tài)分表配置...");
        updateShardingTableNodes();
    }


    /**
     * 獲取所有用戶相關(guān)的表名
     * 此方法旨在動態(tài)地收集所有用戶表的表名,以支持可能存在的不同性別用戶表
     * 如果無法獲取動態(tài)表名或列表為空,則默認(rèn)返回包含單一的默認(rèn)用戶表名"user"
     *
     * @return 包含所有用戶表名的集合
     */
    private Set<String> fetchAllUserTableNames() {
        //獲取所有動態(tài)化表名
        Set<String> tableNames = new HashSet<>();
        try {
            // 獲取用戶列表
            List<User> users = userService.list();
            // 如果用戶列表不為空,則映射每個用戶到對應(yīng)的表名,并收集到集合中
            if (users != null) {
                tableNames = users.stream()
                        .map(user -> "user_" + user.getGender())
                        .collect(Collectors.toSet());
            }
            // 確保至少包含默認(rèn)表
            tableNames.add("user");
        } catch (Exception e) {
            // 記錄獲取表名時發(fā)生的錯誤
            log.error("獲取所有動態(tài)化表名失敗", e);
            // 發(fā)生異常時至少返回默認(rèn)表
            tableNames.add("user");
        }
        // 返回收集到的表名集合
        return tableNames;
    }


    /**
     * 動態(tài)更新分片表節(jié)點配置
     * 
     * 本方法旨在根據(jù)當(dāng)前的用戶表名稱,動態(tài)地更新分片表的節(jié)點配置
     * 它首先獲取所有用戶表的名稱,然后構(gòu)建新的分片表節(jié)點配置,并嘗試更新到數(shù)據(jù)庫的元數(shù)據(jù)中
     */
    private void updateShardingTableNodes() {
        try {
            // 獲取所有用戶表的名稱
            Set<String> tableNames = fetchAllUserTableNames();
            if (tableNames.isEmpty()) {
                // 如果未獲取到任何表名,則使用默認(rèn)的表配置
                log.warn("未獲取到任何表名,將使用默認(rèn)表配置");
                tableNames.add("user");
            }
    
            // 確保包含所有可能的表
            tableNames.add("user");
            tableNames.add("user_0");
            tableNames.add("user_1");
    
            // 構(gòu)建新的分片表節(jié)點配置
            String newActualDataNodes = tableNames.stream()
                    .distinct()
                    .map(tableName -> "ds0." + tableName)
                    .collect(Collectors.joining(","));
            log.info("動態(tài)分表 actual-data-nodes 配置: {}", newActualDataNodes);
    
            // 獲取 ContextManager 實例
            ContextManager contextManager = getContextManager();
            if (contextManager == null) {
                log.error("獲取 ContextManager 失敗");
                return;
            }
    
            // 獲取 MetaDataContexts 實例
            var metaDataContexts = contextManager.getMetaDataContexts();
            if (metaDataContexts == null) {
                log.error("獲取 MetaDataContexts 失敗");
                return;
            }
    
            // 獲取 MetaData 實例
            var metaData = metaDataContexts.getMetaData();
            if (metaData == null) {
                log.error("獲取 MetaData 失敗");
                return;
            }
    
            // 檢查數(shù)據(jù)庫是否存在
            var databases = metaData.getDatabases();
            if (databases == null || !databases.containsKey(DATABASE_NAME)) {
                log.error("數(shù)據(jù)庫 {} 不存在", DATABASE_NAME);
                return;
            }
    
            // 獲取 ShardingSphere 的規(guī)則元數(shù)據(jù)
            ShardingSphereRuleMetaData ruleMetaData = databases.get(DATABASE_NAME).getRuleMetaData();
            if (ruleMetaData == null) {
                log.error("獲取規(guī)則元數(shù)據(jù)失敗");
                return;
            }
    
            // 查找 ShardingRule
            Optional<ShardingRule> shardingRule = ruleMetaData.findSingleRule(ShardingRule.class);
            if (shardingRule.isPresent()) {
                // 獲取分片規(guī)則配置
                ShardingRuleConfiguration ruleConfig = (ShardingRuleConfiguration) shardingRule.get().getConfiguration();
                if (ruleConfig.getTables() == null || ruleConfig.getTables().isEmpty()) {
                    log.error("分片規(guī)則配置為空");
                    return;
                }
    
                // 更新分片表規(guī)則配置
                List<ShardingTableRuleConfiguration> updatedRules = ruleConfig.getTables()
                        .stream()
                        .map(oldTableRule -> {
                            if (LOGIC_TABLE_NAME.equals(oldTableRule.getLogicTable())) {
                                ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(LOGIC_TABLE_NAME, newActualDataNodes);
                                newTableRuleConfig.setDatabaseShardingStrategy(oldTableRule.getDatabaseShardingStrategy());
                                newTableRuleConfig.setTableShardingStrategy(oldTableRule.getTableShardingStrategy());
                                newTableRuleConfig.setKeyGenerateStrategy(oldTableRule.getKeyGenerateStrategy());
                                newTableRuleConfig.setAuditStrategy(oldTableRule.getAuditStrategy());
                                return newTableRuleConfig;
                            }
                            return oldTableRule;
                        })
                        .collect(Collectors.toList());
                ruleConfig.setTables(updatedRules);
                
                // 嘗試更新分片規(guī)則配置
                try {
                    contextManager.alterRuleConfiguration(DATABASE_NAME, Collections.singleton(ruleConfig));
                    contextManager.reloadDatabase(DATABASE_NAME);
                    log.info("動態(tài)分表規(guī)則更新成功!");
                } catch (Exception e) {
                    log.error("更新分片規(guī)則失敗", e);
                }
            } else {
                log.error("未找到 ShardingSphere 的分片規(guī)則配置,動態(tài)分表更新失敗。");
            }
        } catch (Exception e) {
            log.error("更新分片規(guī)則時發(fā)生異常", e);
        }
    }

    /**
     * 獲取 ShardingSphere ContextManager
     */
    private ContextManager getContextManager() {
        try {
            if (dataSource == null) {
                log.error("數(shù)據(jù)源未注入");
                return null;
            }
            
            var connection = dataSource.getConnection();
            if (connection == null) {
                log.error("獲取數(shù)據(jù)庫連接失敗");
                return null;
            }

            ShardingSphereConnection shardingConnection = connection.unwrap(ShardingSphereConnection.class);
            if (shardingConnection == null) {
                log.error("無法獲取 ShardingSphereConnection");
                connection.close();
                return null;
            }

            ContextManager contextManager = shardingConnection.getContextManager();
            connection.close();
            return contextManager;
        } catch (SQLException e) {
            log.error("獲取 ShardingSphere ContextManager 失敗", e);
            return null;
        }
    }

    /**
     * 根據(jù)用戶信息創(chuàng)建用戶表
     * 表名基于用戶創(chuàng)建時間生成,格式為:LOGIC_TABLE_NAME_YYYYMM
     * 如果表已存在,則不進(jìn)行創(chuàng)建操作
     * 
     * @param user 用戶對象,包含用戶創(chuàng)建時間等信息
     */
    public void createUserTable(User user) {
        // 獲取用戶創(chuàng)建時間
        Date createTime = user.getCreatetime();
        // 創(chuàng)建日期格式化對象,用于生成表名
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
        // 生成完整的表名
        String tableName = LOGIC_TABLE_NAME + "_" + dateFormat.format(createTime);
        
        try {
            // 首先檢查表是否已存在
            String checkTableSql = "SHOW TABLES LIKE '" + tableName + "'";
            List<Map<String, Object>> tables = SqlRunner.db().selectList(checkTableSql);
            
            // 如果表存在,記錄日志并結(jié)束方法
            if (tables != null && !tables.isEmpty()) {
                log.info("表 {} 已經(jīng)存在,無需創(chuàng)建", tableName);
                return;
            }
            
            // 創(chuàng)建表
            String createTableSql = "CREATE TABLE IF NOT EXISTS " + tableName + " LIKE user";
            log.info("開始創(chuàng)建表,SQL: {}", createTableSql);
            
            SqlRunner.db().update(createTableSql);
            log.info("表 {} 創(chuàng)建成功", tableName);
            
            // 更新分片配置
            updateShardingTableNodes();
        } catch (Exception e) {
            log.error("創(chuàng)建分表 {} 失敗: {}", tableName, e.getMessage(), e);
            // 檢查異常消息,如果表已存在,則記錄日志并結(jié)束方法
            if (e.getMessage() != null && e.getMessage().contains("already exists")) {
                log.info("表 {} 已經(jīng)存在,繼續(xù)處理", tableName);
                return;
            }
            // 如果異常與表已存在無關(guān),則拋出運行時異常
            throw new RuntimeException("創(chuàng)建分表失敗: " + e.getMessage(), e);
        }
    }

}
package com.hhh.sharding.standa;

import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Properties;

@Slf4j
public class UserShardingAlgorithm implements StandardShardingAlgorithm<Date> {
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMM");

    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) {
        Date createTime = preciseShardingValue.getValue();
        String logicTableName = preciseShardingValue.getLogicTableName();
        
        log.info("分片算法執(zhí)行 - 可用目標(biāo)表: {}, 分片值: {}, 邏輯表名: {}", 
                availableTargetNames, createTime, logicTableName);
        
        if (createTime == null) {
            log.info("createTime為空,返回邏輯表名: {}", logicTableName);
            return logicTableName;
        }
        
        // 根據(jù) createTime 動態(tài)生成分表名
        String suffix = DATE_FORMAT.format(createTime);
        String realTableName = "user_" + suffix;
        log.info("計算得到的實際表名: {}", realTableName);
        
        if (availableTargetNames.contains(realTableName)) {
            log.info("找到匹配的目標(biāo)表: {}", realTableName);
            return realTableName;
        } else {
            log.warn("未找到匹配的目標(biāo)表,返回邏輯表名: {}", logicTableName);
            return logicTableName;
        }
    }

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Date> rangeShardingValue) {
        return new ArrayList<>();
    }

    @Override
    public Properties getProps() {
        return new Properties();
    }

    @Override
    public void init(Properties properties) {
        // 可以添加初始化邏輯
    }
}

4.測試用例

package com.hhh.sharding.controller;

import cn.hutool.core.util.RandomUtil;
import com.hhh.sharding.domain.User;
import com.hhh.sharding.service.UserService;
import com.hhh.sharding.standa.DynamicShardingManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.text.SimpleDateFormat;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @Resource
    private DynamicShardingManager dynamicShardingManager;

    @GetMapping("/add")
    public Boolean user() {
        // 創(chuàng)建一些2024年的隨機(jī)日期
        Date[] dates = {
            getDate("2024-01-15"),
            getDate("2024-02-20"),
            getDate("2024-03-10"),
            getDate("2024-04-05"),
            getDate("2024-05-25")
        };
        
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setUsername(generateRandomUsername());
            user.setPassword("123456");
            user.setGender(RandomUtil.randomInt(2));
            // 隨機(jī)選擇一個2024年的日期
            Date randomDate = dates[RandomUtil.randomInt(dates.length)];
            user.setCreatetime(randomDate);
            user.setUpdatetime(randomDate);
            //這里每一次新增數(shù)據(jù)的時候去判斷是否要創(chuàng)建出來當(dāng)月的數(shù)據(jù)表,這張表一定要在    
            //application.yml中的actual-data-nodes中去添加
            dynamicShardingManager.createUserTable(user);
            userService.save(user);
        }
        return true;
    }

    private Date getDate(String dateStr) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
        } catch (Exception e) {
            return new Date();
        }
    }

    // 生成10位隨機(jī)數(shù)字的用戶名
    private String generateRandomUsername() {
        return RandomUtil.randomNumbers(10);  // 生成10位數(shù)字
    }

    @GetMapping("/all")
    public List<User> all() {
        return userService.list();
    }
}

5.測試結(jié)果

  • 新增數(shù)據(jù)

  • 查詢數(shù)據(jù)

  • 數(shù)據(jù)庫情況

  • 數(shù)據(jù)庫表數(shù)據(jù)展示

總結(jié)

由于公司有一個需求那就是按月來分表展示數(shù)據(jù),看了好多人的文章都沒有效果,最終三天得以解決這個功能,故而寫下此文章。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • mybatis一直加載xml,找到錯誤的解決方案

    mybatis一直加載xml,找到錯誤的解決方案

    這篇文章主要介紹了mybatis一直加載xml,找到錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • SpringMVC 使用JSR-303進(jìn)行校驗 @Valid示例

    SpringMVC 使用JSR-303進(jìn)行校驗 @Valid示例

    本篇文章主要介紹了SpringMVC 使用JSR-303進(jìn)行校驗 @Valid示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Java中二維數(shù)組的正確使用方法介紹

    Java中二維數(shù)組的正確使用方法介紹

    Java中有一維數(shù)組,二維數(shù)組以及多維數(shù)組,在這篇文章中,將給大家詳細(xì)介紹一下如何正確使用Java中的二維數(shù)組,感興趣的小伙伴跟著小編一起學(xué)習(xí)吧
    2023-05-05
  • 關(guān)于@Value注解取不到值的幾種情況

    關(guān)于@Value注解取不到值的幾種情況

    這篇文章主要介紹了關(guān)于@Value注解取不到值的幾種情況,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot集成Tomcat服務(wù)架構(gòu)配置

    SpringBoot集成Tomcat服務(wù)架構(gòu)配置

    這篇文章主要為大家介紹了SpringBoot集成Tomcat服務(wù)架構(gòu)配置,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • java 如何為文件及文件夾添加權(quán)限

    java 如何為文件及文件夾添加權(quán)限

    這篇文章主要介紹了java 如何為文件及文件夾添加權(quán)限的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java中的動態(tài)數(shù)組和棧Vector Stack使用區(qū)別介紹

    Java中的動態(tài)數(shù)組和棧Vector Stack使用區(qū)別介紹

    這篇文章主要為大家介紹了Java中的動態(tài)數(shù)組和棧Vector Stack使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Java代碼中如何設(shè)置輸出字符集為UTF-8

    Java代碼中如何設(shè)置輸出字符集為UTF-8

    這篇文章主要介紹了Java代碼中設(shè)置輸出字符集為UTF-8,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • maven配置阿里云倉庫的實現(xiàn)方法(2022年)

    maven配置阿里云倉庫的實現(xiàn)方法(2022年)

    本文主要介紹了maven配置阿里云倉庫的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java多線程之Park和Unpark原理

    Java多線程之Park和Unpark原理

    這篇文章主要介紹了Java多線程之Park和Unpark原理,需文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,要的朋友可以參考下
    2021-04-04

最新評論