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