前言
- 本項目分表方案是按照時間字段按日分表 其他分表方案也可參考本文檔實現自動建表
- 需要提前准備待分表的主表寫入數據庫
- 優勢:
可以實現自動建表 且不需要配置 SQL
范圍分表查詢時自動排除不存在的表
配置
主要依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- huTool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.7</version>
</dependency>
<!-- sharding jdbc-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
import com.**.**.dao.CommonMapper;
import com.**.**.domain.db.CreateTableSql;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import java.util.HashSet;
import java.util.List;
/**
* 分表工具
*/
@Slf4j
public abstract class ShardingAlgorithmTool<T extends Comparable<?>> implements PreciseShardingAlgorithm<T>, RangeShardingAlgorithm<T> {
private static CommonMapper commonMapper;
private static final HashSet<String> tableNameCache = new HashSet<>();
/**
* 手動注入
*/
public static void setCommonMapper(CommonMapper commonMapper) {
ShardingAlgorithmTool.commonMapper = commonMapper;
}
/**
* 判斷 分表獲取的表名是否存在 不存在則自動建表
*
* @param logicTableName 邏輯表名(表頭)
* @param resultTableName 真實表名
* @return 確認存在於數據庫中的真實表名
*/
public String shardingTablesCheckAndCreatAndReturn(String logicTableName, String resultTableName) {
synchronized (logicTableName.intern()) {
// 緩存中有此表 返回
if (shardingTablesExistsCheck(resultTableName)) {
return resultTableName;
}
// 緩存中無此表 建表 並添加緩存
CreateTableSql createTableSql = commonMapper.selectTableCreateSql(logicTableName);
String sql = createTableSql.getCreateTable();
sql = sql.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
sql = sql.replace(logicTableName, resultTableName);
commonMapper.executeSql(sql);
tableNameCache.add(resultTableName);
}
return resultTableName;
}
/**
* 判斷表是否存在於緩存中
*
* @param resultTableName 表名
* @return 是否存在於緩存中
*/
public boolean shardingTablesExistsCheck(String resultTableName) {
return tableNameCache.contains(resultTableName);
}
/**
* 緩存重載方法
*
* @param schemaName 待加載表名所屬數據庫名
*/
public static void tableNameCacheReload(String schemaName) {
// 讀取數據庫中所有表名
List<String> tableNameList = commonMapper.getAllTableNameBySchema(schemaName);
// 刪除舊的緩存(如果存在)
ShardingAlgorithmTool.tableNameCache.clear();
// 寫入新的緩存
ShardingAlgorithmTool.tableNameCache.addAll(tableNameList);
}
}
分表實現類 DateShardingAlgorithm
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 日期分表策略
*/
public class DateShardingAlgorithm extends ShardingAlgorithmTool<Date> {
/**
* 獲取 指定分表
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) {
return shardingTablesCheckAndCreatAndReturn(preciseShardingValue.getLogicTableName(), preciseShardingValue.getLogicTableName() + DateUtil.format(preciseShardingValue.getValue(), "_yyyy_MM_dd"));
}
/**
* 獲取 范圍分表
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> rangeShardingValue) {
Range<Date> valueRange = rangeShardingValue.getValueRange();
Date lowerDate = valueRange.lowerEndpoint();
Date upperDate = valueRange.upperEndpoint();
List<String> tableNameList = new ArrayList<>();
for (DateTime dateTime : DateUtil.rangeToList(DateUtil.beginOfDay(lowerDate), DateUtil.endOfDay(upperDate), DateField.DAY_OF_YEAR)) {
String resultTableName = rangeShardingValue.getLogicTableName() + DateUtil.format(dateTime, "_yyyy_MM_dd");
if (shardingTablesExistsCheck(resultTableName)) {
tableNameList.add(resultTableName);
}
}
return tableNameList;
}
}
項目啟動時將表載入緩存/注入工具類屬性 ShardingTablesLoadRunner
import com.**.**.config.sharding.ShardingAlgorithmTool;
import com.**.**.dao.CommonMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 項目啟動后 讀取已有分表 進行緩存
*/
@Slf4j
@Order(value = 1) // 數字越小 越先執行
@Component
public class ShardingTablesLoadRunner implements CommandLineRunner {
@Value("${db.schema-name}")
private String schemaName;
@Resource
private CommonMapper commonMapper;
@Override
public void run(String... args) throws Exception {
// 給 分表工具類注入屬性
ShardingAlgorithmTool.setCommonMapper(commonMapper);
// 調用緩存重載方法
ShardingAlgorithmTool.tableNameCacheReload(schemaName);
log.info("ShardingTablesLoadRunner start OK");
}
}
Mybatis SQL 映射 CommonMapper
import com.**.**.domain.db.CreateTableSql;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 常用工具 mapper
*/
public interface CommonMapper {
/**
* 查詢數據庫中的所有表名
*
* @param schema 數據庫名
* @return 表名列表
*/
List<String> getAllTableNameBySchema(@Param("schema") String schema);
/**
* 查詢建表語句
*
* @param tableName 表名
* @return 建表語句
*/
CreateTableSql selectTableCreateSql(@Param("tableName") String tableName);
/**
* 執行SQL
*
* @param sql 待執行SQL
*/
void executeSql(@Param("sql") String sql);
}
Mybatis SQL 映射 XML CommonMapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.**.**.dao.CommonMapper">
<resultMap id="selectTableCreateSqlResultMap" type="com.**.**.domain.db.CreateTableSql">
<result column="Table" property="table"/>
<result column="Create Table" property="createTable"/>
</resultMap>
<select id="getAllTableNameBySchema" resultType="java.lang.String">
SELECT TABLES.TABLE_NAME
FROM information_schema.TABLES
WHERE TABLES.TABLE_SCHEMA = #{schema}
</select>
<select id="selectTableCreateSql" resultMap="selectTableCreateSqlResultMap">
SHOW CREATE TABLE ${tableName}
</select>
<update id="executeSql">
${sql}
</update>
</mapper>
Mybatis SQL 映射實體 CreateTableSql
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 建表語句查詢結果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CreateTableSql {
private String table;
private String createTable;
}