@[toc]
前言
前段时间我不是做MP的动态表名嘛,详见Mybatis-Plus 动态表名,然后我去MP的动态表名的demo中看到了动态表名的传值方式,没错就是ThreadLocal。
- 这个是他的传递辅助类
public class RequestDataHelper {
/**
* 请求参数存取
*/
private static final ThreadLocal<Map<String, Object>> REQUEST_DATA = new ThreadLocal<>();
/**
* 设置请求参数
*
* @param requestData 请求参数 MAP 对象
*/
public static void setRequestData(Map<String, Object> requestData) {
REQUEST_DATA.set(requestData);
}
/**
* 获取请求参数
*
* @param param 请求参数
* @return 请求参数 MAP 对象
*/
public static <T> T getRequestData(String param) {
Map<String, Object> dataMap = getRequestData();
if (CollectionUtils.isNotEmpty(dataMap)) {
return (T) dataMap.get(param);
}
return null;
}
}
- 附 我的替换表名的拦截器代码
@Configuration
@MapperScan("com.cars.ysdd.clts.domain.clts.dao")
public class MybatisPlusConfig {
static List<String> tableList(){
List<String> tables = new ArrayList<>();
tables.add("user");
return tables;
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
// 获取参数方法
String newTable = null;
for (String table : tableList()) {
newTable = RequestDataHelper.getRequestData(table);
if (table.equals(tableName) && newTable!=null){
tableName = newTable;
break;
}
}
return tableName;
});
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
}
然后代码中是这么使用的。
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("user", "user_2018");
}});
那会儿就纳闷如果有并发的话,肯定会出问题啊,然后我就想试试,于是我新开了个线程进行赋值。
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("user", "user_2019");
}});
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
RequestDataHelper.setRequestData(new HashMap<String, Object>() {{
put("user", "user_2018");
}});
});
completableFuture.get();
User user = userMapper.selectById(1);
//sql-> select * from user_2019 where id = 1
最后发现执行的sql表名居然还是user_2019,可明明user_2018是最后赋值的呀,带着疑惑我去研究了。
ThreadLocal
后来一想,ThreadLocal这个类名可以顾名思义的进行理解,表示线程的“本地变量”,即每个线程都拥有该变量副
本,达到人手一份的效果,各用各的这样就可以避免共享资源的竞争。
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,
在多线程环境下,如何防止自己的变量被其它线程篡改。
有什么问题?
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。
只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,
一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
解决办法
最后给他清空就行了
REQUEST_DATA .remove();