mybatis-plus动态分表使用ThreadLocal未remove引起的问题

背景

使用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溢出风险。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis-plus 也支持分表操作,具体实现方式如下: 1. 定义数据源和 Mybatis-plus 相关依赖: ```xml <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> </dependencies> ``` 2. 定义分表规则,可以使用自定义的分表策略,也可以使用 Mybatis-plus 提供的默认分表策略: ```java public class MyTableNameHandler implements ITableNameHandler { @Override public String dynamicTableName(String sql, Object param) { if (param instanceof Long) { return "table_" + (Long) param % 2; } return null; } } ``` 3. 配置分表策略: ```java @Configuration public class MybatisPlusConfig { @Bean public MyTableNameHandler myTableNameHandler() { return new MyTableNameHandler(); } @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } @Bean public DynamicTableNameParser dynamicTableNameParser(MyTableNameHandler myTableNameHandler) { DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser(); dynamicTableNameParser.setTableNameHandlerMap(Collections.singletonMap("my_table", myTableNameHandler)); return dynamicTableNameParser; } } ``` 4. 在实体类中使用 `@TableName` 注解指定表名: ```java @Data @TableName(value = "my_table") public class MyEntity { @TableId private Long id; private String name; } ``` 5. 在 Mapper 接口中使用 `@SqlParser` 注解开启分表功能: ```java @Mapper public interface MyMapper extends BaseMapper<MyEntity> { @SqlParser(parser = DynamicTableNameParser.class) List<MyEntity> selectListByUserId(@Param("userId") Long userId); } ``` 在执行查询操作时,Mybatis-plus 会根据参数动态生成分表 SQL,并将其交给数据源执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值