最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MyBatis-Plus 動態(tài)表名的正確使用方式

 更新時間:2026年02月28日 10:33:00   作者:老馬9527  
本文主要介紹了MyBatis-Plus 動態(tài)表名的正確使用方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

解決的痛點

? 在我們?nèi)粘i_發(fā)中,經(jīng)常會遇到某個表的數(shù)據(jù)量非常大,需要按照年/月進行分表的情況。比如訂單表、SN表等等。如何利用MybatisPlus的動態(tài)表名插件、以及如何進行使用,都比較繁瑣。這里提供的動態(tài)表名的使用方式,是以MybatisPlus的動態(tài)表名插件為基礎(chǔ)構(gòu)建的。核心特性包括:

  • 基于 MyBatis-Plus 官方動態(tài)表名插件
  • 白名單機制,防止任意表名注入
  • ThreadLocal 作用域自動清理,避免線程污染
  • 提供 try-with-resources函數(shù)式 API 兩種使用方式

使用方式

使用方式力求簡潔,并且要保證在使用動態(tài)表名后,能動態(tài)清除表名。不留內(nèi)存碎片。這里提供兩個標準方式使用,示例中采用多數(shù)據(jù)源進行演示。

  • 指定表名并自動清除

    // A庫中的2025年訂單表
    try (DynamicTableNameHelper.Scope ignore = DynamicTableNameHelper.use("t_xx_order_2025")) {
        OrderEntity order2025 = orderMapper.selectById(1984555429137637378L);
        System.out.println(order2025);
    }
    // A庫中的2024年訂單表
    try (DynamicTableNameHelper.Scope ignore = DynamicTableNameHelper.use("t_xx_order_2024")) {
        OrderEntity order2024 = orderMapper.selectById(2001114675221204994L);
        System.out.println(order2024);
    }
    // 默認庫中的商品表
    ProductInfoEntity productInfo = this.productInfoMapper.selectById(1L);
    System.out.println(productInfo);
    
  • 函數(shù)式表名并自動清除

    // A庫中的2025年訂單表
    OrderEntity order2025 = DynamicTableNameHelper.withTable("t_xx_order_2025", () -> orderMapper.selectById(1984555429137637378L));
    System.out.println(order2025);
    // A庫中的2024年訂單表
    OrderEntity order2024 = DynamicTableNameHelper.withTable("t_xx_order_2024", () -> orderMapper.selectById(2001114675221204994L));
    System.out.println(order2024);
    // 默認庫中的產(chǎn)品表
    ProductInfoEntity productInfoEntity = this.productInfoMapper.selectById(1L);
    System.out.println(productInfoEntity);
    

示例中特意采用了多數(shù)據(jù)源進行演示,目的想說明這個動態(tài)表名和多數(shù)據(jù)源之間并不沖突。

上面兩種使用方式,沒有好壞之分。僅僅是使用習慣而已。就我而且可能更傾向于使用代碼更簡潔的第2中方式。

使用方式適合場景
try-with-resources多條 SQL、復雜邏輯、跨方法調(diào)用
withTable單次查詢 / 插入 / 更新

如何做到

這里就要結(jié)合MybatisPlus的動態(tài)表名插件,所以這里會一步一步,在Springboot項目中把實現(xiàn)方式列舉出來。

動態(tài)表名白名單

為了想攔截需要進行動態(tài)的表名,這里采用配置文件中進行配置的方式。如果配置了就行攔截,否則也沒有什么影響。

/**
 * 配置的動態(tài)表名白名單
 *
 * @author 老馬
 */
@Data
@ConfigurationProperties(prefix = "ums.database.dynamic-table")
public class DynamicTableProperties {

    /**
     * 允許使用動態(tài)表名的表(邏輯表名)
     */
    private Set<String> tables = new HashSet<>();
}

這里對應使用時的配置:

ums:
  database:
    dynamic-table:
      tables:
        - t_xx_order
        - t_xx_sn

說明:

  • 這里配置的是 邏輯表名
  • 采用 前綴匹配策略
  • 示例中:
    • t_xx_order_2024
    • t_xx_order_2025 都會被允許
  • 如果使用了DynamicTableNameHelper類,但提供的又不是動態(tài)表名白名單中的表名,那么會提示錯誤

動態(tài)表名

該類,最重要的作用就是判斷MybatisPlus的動態(tài)表名插件傳入的表名是不是在配置的白名單中。

/**
 * 動態(tài)表名白名單
 *
 * @author 老馬
 */
public class DynamicTables {

    private static Set<String> TABLES = Collections.emptySet();

    private DynamicTables() {
    }

    static void init(Set<String> tables) {
        // 創(chuàng)建不可更改的Set
        TABLES = Collections.unmodifiableSet(tables);
    }

    /**
     * 是否允許使用動態(tài)表名
     */
    public static boolean isDynamic(String tableName) {
        if (!StringUtils.hasText(tableName)) {
            return false;
        }
        return TABLES.stream().anyMatch(tableName::startsWith);
    }
}

說明:

  • 使用不可變 Set,避免運行期被修改
  • 通過前綴匹配支持多張物理分表
  • 所有動態(tài)表名必須命中白名單

mybatis-plus配置類

核心的配置類,這里重點關(guān)注初始化動態(tài)表名白名單和動態(tài)表名插件的處理。

/**
 * mybatis-plus配置類
 *
 * @author 老馬
 */
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(DynamicTableProperties.class)
public class MybatisPlusConfig {

    private final DynamicTableProperties dynamicTableProperties;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 動態(tài)表名插件
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
        // 其他插件
        return interceptor;
    }

    /**
     * 動態(tài)表名插件
     */
    private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
        TableNameHandler tableNameHandler = (sql, tableName) -> {
            // 不在白名單,直接返回原表名
            if (!DynamicTables.isDynamic(tableName)) {
                return tableName;
            }
            // 取當前線程綁定的動態(tài)表名
            String dynamicTableName = DynamicTableNameHelper.get();

            //  沒有設(shè)置動態(tài)表名,兜底返回原表名
            return StringUtils.hasText(dynamicTableName)
                    ? dynamicTableName
                    : tableName;
        };
        return new DynamicTableNameInnerInterceptor(tableNameHandler);

    }

    /**
     * 初始化動態(tài)表名白名單
     */
    @PostConstruct
    public void initDynamicTables() {
        DynamicTables.init(dynamicTableProperties.getTables());
    }
}

動態(tài)表名助手類

/**
 * 動態(tài)表名輔助類
 *
 * @author 銀商北分-老馬
 * @since 1.0.0
 */
public final class DynamicTableNameHelper {

    /**
     * 表名正則
     */
    public static final String TABLE_NAME_REGEX = "[a-zA-Z0-9_]+";

    private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();

    private DynamicTableNameHelper() {}

    /**
     * 使用動態(tài)表名
     *
     * @param tableName 表名
     * @return 作用域
     */
    public static Scope use(String tableName) {
        validate(tableName);
        if (!DynamicTables.isDynamic(tableName)) {
            throw new RuntimeException("表 [" + tableName + "] 未配置為允許動態(tài)表名");
        }
        String old = HOLDER.get();
        HOLDER.set(tableName);
        return () -> {
            if (old == null) {
                HOLDER.remove();
            } else {
                HOLDER.set(old);
            }
        };
    }

    /**
     * 在指定的動態(tài)表名作用域內(nèi)執(zhí)行操作
     *
     * @param table    表名
     * @param supplier 執(zhí)行邏輯
     * @param <T>      返回值類型
     * @return 返回值
     */
    public static <T> T withTable(String table, Supplier<T> supplier) {
        try (Scope ignored = use(table)) {
            return supplier.get();
        }
    }

    /**
     * 在指定的動態(tài)表名作用域內(nèi)執(zhí)行操作(無返回值)
     *
     * @param table    表名
     * @param runnable 執(zhí)行邏輯
     */
    public static void withTable(String table, Runnable runnable) {
        try (Scope ignored = use(table)) {
            runnable.run();
        }
    }

    /**
     * 獲取當前作用域的表名
     *
     * @return 表名
     */
    public static String get() {
        return HOLDER.get();
    }

    private static void validate(String tableName) {
        if (tableName == null || tableName.isBlank()) {
            throw new RuntimeException("tableName 不能為空");
        }
        if (!tableName.matches(TABLE_NAME_REGEX)) {
            throw new RuntimeException("非法表名:" + tableName);
        }
    }

    @FunctionalInterface
    public interface Scope extends AutoCloseable {
        /**
         * 關(guān)閉作用域
         */
        @Override
        void close();
    }
}

說明:

  • 動態(tài)表名通過 ThreadLocal 保存
  • 通過作用域模式確保 set / remove 成對執(zhí)行
  • 避免線程池復用導致的表名污染問題
  • 另外還支持嵌套調(diào)用
// 嵌套調(diào)用示例
try (Scope s1 = use("t_xx_order_2025")) {
    // 查詢 2025
    try (Scope s2 = use("t_xx_order_2024")) {
        // 查詢 2024
    }
    // 自動恢復為 2025
}

實體類

@Data
@TableName("t_xx_order")
public class OrderEntity implements Serializable {
    /**
     * 主鍵
     */
    @TableId
    private Long id;

    /**
     * 下單日期,格式:yyyy-MM-dd
     */
    private String orderCreateDate;

    /**
     * 訂單號
     */
    private String orderno;
    
    // ...省略其他屬性
}

注意:

這里特別強調(diào)一下,這個動態(tài)表,一定要用@TableName注解告訴MybatisPlus的動態(tài)表名組件,邏輯表名叫什么。也就是我們這里的@TableName("t_xx_order")。否則無法拼接完成表名。默認MybatisPlus通過類,不會有前面的"t_xx_"。之后映射為order_entity,這種表名,那么在執(zhí)行時就會報表或者視圖不存在的錯誤了。

避坑指南

本方案的動態(tài)表名能力是基于 ThreadLocal 實現(xiàn)的,因此在使用時需要特別注意線程邊界問題。

不支持的場景

以下場景中,動態(tài)表名不會自動生效,甚至可能出現(xiàn)查錯表的風險:

  • @Async 標注的方法
  • 手動使用線程池(ExecutorService.submit / execute)
  • CompletableFuture(使用默認或自定義線程池)
  • 任何發(fā)生 線程切換 的異步執(zhí)行場景

原因在于: ThreadLocal 中保存的動態(tài)表名 不會在線程之間自動傳遞。

錯誤示例

DynamicTableNameHelper.withTable("t_xx_order_2025", () -> {
    asyncService.doAsyncQuery(); // @Async 方法
});

上述代碼中,doAsyncQuery 方法運行在新的線程中,此時動態(tài)表名上下文已經(jīng)丟失,最終仍然會訪問邏輯表名對應的默認表。

正確使用方式

@Async
public void doAsyncQuery() {
    DynamicTableNameHelper.withTable("t_xx_order_2025", () -> {
        orderMapper.selectById(1L);
    });
}

到此這篇關(guān)于MyBatis-Plus 動態(tài)表名的正確使用方式的文章就介紹到這了,更多相關(guān)MyBatisPlus 動態(tài)表名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

同德县| 横山县| 南昌县| 长沙县| 元阳县| 江城| 恭城| 合江县| 娱乐| 福清市| 昭觉县| 开远市| 桃园市| 玉屏| 香港| 平舆县| 吴忠市| 平乡县| 烟台市| 龙井市| 逊克县| 新河县| 开江县| 龙里县| 铜川市| 苍梧县| 尼玛县| 闵行区| 隆子县| 嵊泗县| 平凉市| 章丘市| 成安县| 莱芜市| 股票| 武山县| 镇坪县| 天台县| 天峻县| 鹤峰县| 永清县|