当前位置: 代码网 > it编程>编程语言>Java > MyBatis-Plus 动态表名的正确使用方式

MyBatis-Plus 动态表名的正确使用方式

2026年02月28日 Java 我要评论
解决的痛点​ 在我们日常开发中,经常会遇到某个表的数据量非常大,需要按照年/月进行分表的情况。比如订单表、sn表等等。如何利用mybatisplus的动态表名插件、以及如何进行使用,都比较繁琐。这里提

解决的痛点

​ 在我们日常开发中,经常会遇到某个表的数据量非常大,需要按照年/月进行分表的情况。比如订单表、sn表等等。如何利用mybatisplus的动态表名插件、以及如何进行使用,都比较繁琐。这里提供的动态表名的使用方式,是以mybatisplus的动态表名插件为基础构建的。核心特性包括:

  • 基于 mybatis-plus 官方动态表名插件
  • 白名单机制,防止任意表名注入
  • threadlocal 作用域自动清理,避免线程污染
  • 提供 try-with-resources函数式 api 两种使用方式

使用方式

使用方式力求简洁,并且要保证在使用动态表名后,能动态清除表名。不留内存碎片。这里提供两个标准方式使用,示例中采用多数据源进行演示。

  • 指定表名并自动清除

    // 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);
    
  • 函数式表名并自动清除

    // 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);
    // 默认库中的产品表
    productinfoentity productinfoentity = this.productinfomapper.selectbyid(1l);
    system.out.println(productinfoentity);
    

示例中特意采用了多数据源进行演示,目的想说明这个动态表名和多数据源之间并不冲突。

上面两种使用方式,没有好坏之分。仅仅是使用习惯而已。就我而且可能更倾向于使用代码更简洁的第2中方式。

使用方式适合场景
try-with-resources多条 sql、复杂逻辑、跨方法调用
withtable单次查询 / 插入 / 更新

如何做到

这里就要结合mybatisplus的动态表名插件,所以这里会一步一步,在springboot项目中把实现方式列举出来。

动态表名白名单

为了想拦截需要进行动态的表名,这里采用配置文件中进行配置的方式。如果配置了就行拦截,否则也没有什么影响。

/**
 * 配置的动态表名白名单
 *
 * @author 老马
 */
@data
@configurationproperties(prefix = "ums.database.dynamic-table")
public class dynamictableproperties {

    /**
     * 允许使用动态表名的表(逻辑表名)
     */
    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类,但提供的又不是动态表名白名单中的表名,那么会提示错误

动态表名

该类,最重要的作用就是判断mybatisplus的动态表名插件传入的表名是不是在配置的白名单中。

/**
 * 动态表名白名单
 *
 * @author 老马
 */
public class dynamictables {

    private static set<string> tables = collections.emptyset();

    private dynamictables() {
    }

    static void init(set<string> tables) {
        // 创建不可更改的set
        tables = collections.unmodifiableset(tables);
    }

    /**
     * 是否允许使用动态表名
     */
    public static boolean isdynamic(string tablename) {
        if (!stringutils.hastext(tablename)) {
            return false;
        }
        return tables.stream().anymatch(tablename::startswith);
    }
}

说明:

  • 使用不可变 set,避免运行期被修改
  • 通过前缀匹配支持多张物理分表
  • 所有动态表名必须命中白名单

mybatis-plus配置类

核心的配置类,这里重点关注初始化动态表名白名单和动态表名插件的处理。

/**
 * mybatis-plus配置类
 *
 * @author 老马
 */
@configuration
@requiredargsconstructor
@enableconfigurationproperties(dynamictableproperties.class)
public class mybatisplusconfig {

    private final dynamictableproperties dynamictableproperties;

    @bean
    public mybatisplusinterceptor mybatisplusinterceptor() {
        mybatisplusinterceptor interceptor = new mybatisplusinterceptor();
        // 动态表名插件
        interceptor.addinnerinterceptor(dynamictablenameinnerinterceptor());
        // 其他插件
        return interceptor;
    }

    /**
     * 动态表名插件
     */
    private dynamictablenameinnerinterceptor dynamictablenameinnerinterceptor() {
        tablenamehandler tablenamehandler = (sql, tablename) -> {
            // 不在白名单,直接返回原表名
            if (!dynamictables.isdynamic(tablename)) {
                return tablename;
            }
            // 取当前线程绑定的动态表名
            string dynamictablename = dynamictablenamehelper.get();

            //  没有设置动态表名,兜底返回原表名
            return stringutils.hastext(dynamictablename)
                    ? dynamictablename
                    : tablename;
        };
        return new dynamictablenameinnerinterceptor(tablenamehandler);

    }

    /**
     * 初始化动态表名白名单
     */
    @postconstruct
    public void initdynamictables() {
        dynamictables.init(dynamictableproperties.gettables());
    }
}

动态表名助手类

/**
 * 动态表名辅助类
 *
 * @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() {}

    /**
     * 使用动态表名
     *
     * @param tablename 表名
     * @return 作用域
     */
    public static scope use(string tablename) {
        validate(tablename);
        if (!dynamictables.isdynamic(tablename)) {
            throw new runtimeexception("表 [" + tablename + "] 未配置为允许动态表名");
        }
        string old = holder.get();
        holder.set(tablename);
        return () -> {
            if (old == null) {
                holder.remove();
            } else {
                holder.set(old);
            }
        };
    }

    /**
     * 在指定的动态表名作用域内执行操作
     *
     * @param table    表名
     * @param supplier 执行逻辑
     * @param <t>      返回值类型
     * @return 返回值
     */
    public static <t> t withtable(string table, supplier<t> supplier) {
        try (scope ignored = use(table)) {
            return supplier.get();
        }
    }

    /**
     * 在指定的动态表名作用域内执行操作(无返回值)
     *
     * @param table    表名
     * @param runnable 执行逻辑
     */
    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 {
        /**
         * 关闭作用域
         */
        @override
        void close();
    }
}

说明:

  • 动态表名通过 threadlocal 保存
  • 通过作用域模式确保 set / remove 成对执行
  • 避免线程池复用导致的表名污染问题
  • 另外还支持嵌套调用
// 嵌套调用示例
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;
    
    // ...省略其他属性
}

注意:

这里特别强调一下,这个动态表,一定要用@tablename注解告诉mybatisplus的动态表名组件,逻辑表名叫什么。也就是我们这里的@tablename("t_xx_order")。否则无法拼接完成表名。默认mybatisplus通过类,不会有前面的"t_xx_"。之后映射为order_entity,这种表名,那么在执行时就会报表或者视图不存在的错误了。

避坑指南

本方案的动态表名能力是基于 threadlocal 实现的,因此在使用时需要特别注意线程边界问题。

不支持的场景

以下场景中,动态表名不会自动生效,甚至可能出现查错表的风险:

  • @async 标注的方法
  • 手动使用线程池(executorservice.submit / execute)
  • completablefuture(使用默认或自定义线程池)
  • 任何发生 线程切换 的异步执行场景

原因在于: threadlocal 中保存的动态表名 不会在线程之间自动传递。

错误示例

dynamictablenamehelper.withtable("t_xx_order_2025", () -> {
    asyncservice.doasyncquery(); // @async 方法
});

上述代码中,doasyncquery 方法运行在新的线程中,此时动态表名上下文已经丢失,最终仍然会访问逻辑表名对应的默认表。

正确使用方式

@async
public void doasyncquery() {
    dynamictablenamehelper.withtable("t_xx_order_2025", () -> {
        ordermapper.selectbyid(1l);
    });
}

到此这篇关于mybatis-plus 动态表名的正确使用方式的文章就介绍到这了,更多相关mybatisplus 动态表名内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com