背景

使用myabtis-plus动态分表拦截器,代码实现如下

    @Bean
    public MybatisPlusInterceptor getDynamicTableNameInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
            //这里使用ThreadLocal获取当前线程配置的表
            String curTableName = DynamicTableNameHolder.get();
            return StringUtils.isNotEmpty(curTableName) ? curTableName : tableName;
        });
      	//动态表名拦截器
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        //分页拦截器
        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

注意:如果同时使用分页拦截器,要放在动态表名的下面,否则分页会先执行导致找不到表报错!
官方:https://mybatis.plus/guide/interceptor.html

ThreadLocal相关

public class DynamicTableNameHolder {
    private static final ThreadLocal<String> TABLE_NAME_HOLDER = new ThreadLocal<>();

    public static String get() {
        return TABLE_NAME_HOLDER.get();
    }

    public static void set(String tableName) {
        TABLE_NAME_HOLDER.set(tableName);
    }

    public static void remove() {
        TABLE_NAME_HOLDER.remove();
    }

}

业务使用

        //设置表名
        String dynamicTableName = CollectData.getDynamicTableName(suffix);
        DynamicTableNameHolder.set(dynamicTableName);

并未进行DynamicTableNameHolder.remove();

报错信息

下图左上角是报错的线程,右下角是本次分表的后缀
在这里插入图片描述
sql代码中是没有做分表的,也就是没有用到ThreadLocal设置表后缀,并且是在拦截器中执行的,在所有业务代码之前执行的,根据代码逻辑,mybatiplus会走默认表逻辑。
根据报错信息发现,此sql查错了其他的表,此表的查询是使用了threadlocal的,根据推断此次查询使用了旧的线程的数据,并且此次线程是exec-2,向上排查日志发现exex-2是之前的线程,被复用了。

被复用的线程
被复用的线程

总结

每次请求,tomcat线程会分配线程池进行处理,请求线程任务结束后,线程并不会销毁,而是回收到线程池,线程数据也随之留存,因为在使用threadlocal时,尽量要remove,当然如果是非线程池也可以不remove,但是也会有threadlocal溢出风险。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐