背景说明
一般业务级的SQL会关联用户信息,那么在写SQL是需要将当前用户获取然后注入SQL中,最常见的方法是将用户信息当做条件传入(Map,实体属性、参数值等)
但是这种情况下都会在一定程度上破环了接口的抽象性,不够友好。那么下面考虑使用Mybatis运行时注入参数值。
代码示例
import com.github.pagehelper.util.MetaObjectUtil;
import com.unitechs.framework.context.UserContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* mybatis 动态增加用户id,只针对查询操作有效。
*
* @author zhuyuan 2019-10-24 09:24:25
*/
@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}),
}
)
@Slf4j
public class MybatisSqlParamInterceptor implements Interceptor {
private static final String CURRENT_USER_ID_KEY = "currentUserId";
private static final String CURRENT_USERNAME_KEY = "currentUsername";
private static final int NEW_ADDING_SIZE = 2;
@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();
CacheKey cacheKey;
BoundSql boundSql;
//将参数类型转化为MAP类型,同时将用户信息注入编译SQL
parameter = processParameterObject(ms, parameter);
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
private Object processParameterObject(MappedStatement ms, Object parameterObject) {
Map<String, Object> paramMap;
if (parameterObject == null) {
paramMap = new HashMap(NEW_ADDING_SIZE);
} else if (parameterObject instanceof Map) {
paramMap = new HashMap(((Map) parameterObject).size() + NEW_ADDING_SIZE);
paramMap.putAll((Map) parameterObject);
} else {
paramMap = new HashMap(5);
boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
if (!hasTypeHandler) {
String[] getterNames = metaObject.getGetterNames();
int getterNamesLength = getterNames.length;
for (int i = 0; i < getterNamesLength; ++i) {
String name = getterNames[i];
paramMap.put(name, metaObject.getValue(name));
}
} else {
//注意、只有一个参数情况下只能使用'_parameter'注入了。
paramMap.put("_parameter", parameterObject);
}
return this.processParameter(paramMap);
}
return this.processParameter(paramMap);
}
private Object processParameter(Map<String, Object> paramMap) {
String userId = UserContextHolder.getUserId();
String username = UserContextHolder.getUserName();
if (StringUtils.isNotBlank(userId)) {
log.debug("注入当前用户currentUserId=" + userId + "到查询参数中");
log.debug("注入当前用户currentUsername=" + username + "到查询参数中");
paramMap.put(CURRENT_USER_ID_KEY, Long.valueOf(userId));
paramMap.put(CURRENT_USERNAME_KEY, username);
}
return paramMap;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
代码说明
上文示例只考虑了查询情况下,其他情况请自行补充。