在使用MyBatis-Plus多租户插件时遇到一个问题,同样一个请求查询,有时不会自动拼接租户条件进行查询,可能连续发送几次有一次会是这样,经过对TenantLineInnerInterceptor类的调试跟踪发现了问题所在。

一、问题跟踪

问题就出现在这,在查询之前会进入beforeQuery,在这里InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())表示是否会忽略租户条件,如果为true就表示不添加租户条件。

image.png

最终进入这个willIgnore函数,如果ignoreStrategy不为空就会进入断点部分代码,导致返回为true,从而忽略多租户条件。
image.png

ignoreStrategy是由当前线程变量IGNORE_STRATEGY_LOCAL中获取得到,而设置这个变量的只有handle方法,所以是调用了handle方法导致。
image.png

但实质上IGNORE_STRATEGY_LOCAL是一个ThreadLocal对象,应该只在当前线程有效,而且我当前查询并没有调用handle函数,倒是有其它请求有调用这个方法,所以只有一种可能,线程污染导致。

至于两个不同的请求为什么会线程污染,主要跟我项目依赖有关,我使用的spring是采用tomcat作为web服务器,tomcat对请求的处理采用的是线程池的方式,而不是每个请求都建立一个线程,所以如果在A请求中调用了handle方法,但又没清理,它就会一直留在线程当中,当下次某一个请求B进入,tomcat刚好将那个线程分配给这个请求就会出现这种情况。

二、解决方法

解决方法就是在调用handle函数后记得调用clearIgnoreStrategy进行清理。

1
2
3
4
5
6
7
InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());  
try {
// 业务处理
} catch (Exception e) {
// 清理线程
InterceptorIgnoreHelper.clearIgnoreStrategy();
}

三、问题联想

我这里使用的是spring boot3,默认引入的就是tomcat6,采用的请求线程池是nio形式,在平常的业务中难免会使用ThreadLocal进行业务处理,最好在全局进行清理以防万一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**  
* 全局ThreadLocal清理过滤器
* @author 刘靖
*/
public class ThreadLocalCleanupFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 继续执行请求
chain.doFilter(request, response);
} finally {
// 清理 ThreadLocal 内容
cleanupThreadLocal();
}
}

/**
* 清理 ThreadLocal 中的内容
*/
private void cleanupThreadLocal() {
// 这里清除所有 ThreadLocal 中的内容
TenantThreadLocal.clear();
PermissionThreadLocal.cleanPermission();
DataScopeThreadLocal.clean();
}

}

但由于tomcat6使用的请求线程池是nio,所以如果在一个请求处理过程中有文件之类的io阻塞操作,线程也可能会切换,所以如果一个请求中既有数据库操作或者是依赖ThreadLocal的操作,尽量把io阻塞的操作放到最后。