笔者最近仿照PageHelper写了一个拼装sql的组件,需要灵活拼装多个字段,在遇到多子查询时难以做到通用。笔者最终放弃对sql进行截取拼装,想了一个比较讨巧的方案,仿照Mybatis自定义一个特殊字符,然后在拦截器里替换。不过这种方案也比较坑,需要在sql里列出要过滤的字段,等笔者想出更好的方案时再进行补充,下面先看下用特殊字符的方案。
@Component
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
BoundSql boundSql;
CacheKey cacheKey;
if(args.length == 4){
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
Configuration configuration = ms.getConfiguration();
String sql = this.buildSql(boundSql.getSql());
BoundSql dataFilterBoundSql = new BoundSql(configuration, sql, boundSql.getParameterMappings(), parameter);
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, dataFilterBoundSql);
}
private String buildSql(String sql) {
UserInfo userInfo = UserInfoThreadLocal.get();
//将登陆的用户信息需要过滤的字段放入Map里
Map<String, Object> fieldMap = this.fieldMap(userInfo);
for (Map.Entry<String, Object> entry : fieldMap.entrySet()) {
//替换过滤字段,例#userId
sql = sql.replace("#" + entry.getKey(), entry.getValue().toString());
}
return sql;
}
private Map<String, Object> fieldMap(UserInfo userInfo) {
Class<? extends UserInfo> infoClass = userInfo.getClass();
Map<String, Object> fieldMap = new HashMap<>();
for (Field field : infoClass.getDeclaredFields()) {
try {
field.setAccessible(true);
fieldMap.put(field.getName(), field.get(userInfo));
field.setAccessible(false);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return fieldMap;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {}
上面是拦截器里的代码,Interceptor是Mybatis提供的。笔者是把登陆的userInfo信息里需要自动拼装的字段放入了一个Map里,然后对Map遍历替换掉sql里相应的字段。sql代码段如下:
<select id="queryUserInfo">
select id, name
from User u
where u.id = '#userId'
</select>
sql里的字段是’#userId’,加单引号是防止报错,#+字段名是用来在拦截器里进行替换。