ew是mapper方法里的@Param(Constants.WRAPPER) Wrapper queryWrapper对象
首先判断ew.emptyOfWhere是否存在where条件,有的话再拼接上去,ew.customSqlSegment是WHERE + sql语句
没有where的时候加上 == false
使用${ew.sqlSegment} 如果是连表查询且查询条件是连表的字段则需在service层拼接查询条件时字段前指定别名
sqlSegment:
${ew.sqlSegment}
customSqlSegment:
${ew.customSqlSegment} 则省略
eg: select * from a ${ew.sqlSegment}
select * from a ${ew.customSqlSegment}
注:在对应的dao方法中 一样要指定 @Param(Constants.WRAPPER) Wrapper ew,其中Constants.WRAPPER="ew"源码中可以看到
————————————————
从这里可以看出:表sys_role_dept 的用途,之前一起不清楚 role_dept关联表的含义。
自定义数据权限时(DATA_SCOPE_CUSTOM),通过角色 可以管理哪几个部门,来实现的。
@Aspect
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser))
{
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param userAlias 别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
{
StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
// 自定义数据权限,通过 role_dept, roleid可以查看哪些部门deptid的数据
// 关于 OR 关键字,大胆猜测是考虑 有多个角色时(因为是for),用OR 连接。当然第一个OR 肯定得去掉,(看后面验证)
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
role.getRoleId()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
sqlString.append(StringUtils.format(
" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
deptAlias, user.getDeptId(), user.getDeptId()));
}
else if (DATA_SCOPE_SELF.equals(dataScope))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
// 验证了前面的猜测:一个user多个role时,会拼接多个OR 部门的数据, 猜测第一个OR ,按sql 逻辑,理应去掉。
// 这里substring(4) 去掉第一个" OR "
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
上面拼接后: and dept_id in (), 放到 baseEntity的 params里 。
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/
private void clearDataScope(final JoinPoint joinPoint)
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}
-----------------------------------
项目中需要实现自定义的查询,但是仍然想用QueryWrapper对象里面的那些查询,
在接口中方法中:@Param(Constants.WRAPPER) QueryWrapper queryWrapper; 里面的Constants.WRAPPER其实就是:“ew”
在我们的Mapper.xml文件中:
直接使用:${ew.customSqlSegment} 就可以调用QueryWrapper对象的条件sql
就上面就可以直接用了,下面来分析怎么调用的
首先${ew.customSqlSegment} 这里,我们首先猜测Query Wrapper里面是不是有这个属性,因为我们用过mybaits都知道, 传入一个对象之后,直接对象.属性就可以获取值
————————————————
附上Query Wrapper的关系图
我在Wrapper类中找到了getCustomSqlSegment()方法,所以我猜测肯定是调用了这个get方法,
但是奇怪的是这个类里面并没有customSqlSegment属性字段
public String getCustomSqlSegment() {
MergeSegments expression = this.getExpression();
if (Objects.nonNull(expression)) {
NormalSegmentList normal = expression.getNormal();
String sqlSegment = this.getSqlSegment();
if (StringUtils.isNotBlank(sqlSegment)) {
// 注意这里,如果没有where 条件,不拼接 customSqlSegment
if (normal.isEmpty()) {
return sqlSegment;
}
return "WHERE " + sqlSegment;
}
}
return "";
}
所以猜测是直接调用的这个get方法,根据我们传入的属性名称,拼接成get+属性名称,这样来调用的吗?
所以我就写了一个demo
1、没有属性值
2、但是有一个getUserForId()的方法,
3、在mapper.xml文件中 :对象.userForId 但是注意:我的类里面并没有这个属性
试一下是否可以成功查询出数据!
xml文件
Reflector类中:
构造方法里面,根据该类名获取所有的get方法
private void addSetMethods(Method[] methods) {
Map> conflictingSetters = new HashMap<>();
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
resolveSetterConflicts(conflictingSetters);
}
private void addMethodConflict(Map> conflictingMethods, String name, Method method) {
if (isValidPropertyName(name)) {
List list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>());
list.add(method);
}
}
private void resolveSetterConflicts(Map> conflictingSetters) {
for (Entry> entry : conflictingSetters.entrySet()) {
String propName = entry.getKey();
List setters = entry.getValue();
Class> getterType = getTypes.get(propName);
boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
boolean isSetterAmbiguous = false;
Method match = null;
for (Method setter : setters) {
if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (!isSetterAmbiguous) {
match = pickBetterSetter(match, setter, propName);
isSetterAmbiguous = match == null;
}
}
if (match != null) {
addSetMethod(propName, match);
}
}
}
private void addGetMethod(String name, Method method, boolean isAmbiguous) {
MethodInvoker invoker = isAmbiguous
? new AmbiguousMethodInvoker(method, MessageFormat.format(
"Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
name, method.getDeclaringClass().getName()))
: new MethodInvoker(method);
getMethods.put(name, invoker);
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
调用的时候:根据xml里面的属性名称,在我们上面初始化的getMethods的map中找到对应的方法,执行方法的调用
总结
所以这就是为啥 ${ew.customSqlSegment} 可以调用的原因了