目录
- 一、DataScopeAspect
- 使用场景
- 二、ew.customSqlSegment
- ${ew.customSqlSegment}
- build:
- this.normal : queryWrapper where 条件不为空的时候,才有normal
- get
- 第二次 进来add(), 已经拼接完 ew.customSqlSegment 了, 因为@DataPermission 注解进来的 动态拼接
- Mybatis-plus 在自定义的sql语句中 调用QueryWrapper 的查询条件
- 需求
- 解决
- 分析
- 注意这里,如果没有where 条件,不拼接 customSqlSegment
- 扩展
- end 查看源码中:
- 是根据所有的get方法,经过处理
- 保存到:getMethods中
使用方法:
ew是mapper方法里的@Param(Constants.WRAPPER) Wrapper queryWrapper对象
首先判断ew.emptyOfWhere是否存在where条件,有的话再拼接上去,ew.customSqlSegment是WHERE + sql语句
没有where的时候加上 == false
使用${ew.sqlSegment} 如果是连表查询且查询条件是连表的字段则需在service层拼接查询条件时字段前指定别名
sqlSegment:<where>${ew.sqlSegment}
</where>customSqlSegment:${ew.customSqlSegment} 则省略 <where></where>eg: select * from a<where> ${ew.sqlSegment} </where>select * from a ${ew.customSqlSegment}注:在对应的dao方法中 一样要指定 @Param(Constants.WRAPPER) Wrapper<User> ew,其中Constants.WRAPPER="ew"源码中可以看到
————————————————
一、DataScopeAspect
从这里可以看出:表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, "");}}
}
-----------------------------------
使用场景
二、ew.customSqlSegment
${ew.customSqlSegment}
build:
this.normal : queryWrapper where 条件不为空的时候,才有normal
get
第二次 进来add(), 已经拼接完 ew.customSqlSegment 了, 因为@DataPermission 注解进来的 动态拼接
Mybatis-plus 在自定义的sql语句中 调用QueryWrapper 的查询条件
需求
项目中需要实现自定义的查询,但是仍然想用QueryWrapper对象里面的那些查询,
解决
在接口中方法中:@Param(Constants.WRAPPER) QueryWrapper queryWrapper; 里面的Constants.WRAPPER其实就是:“ew”
在我们的Mapper.xml文件中:
直接使用:${ew.customSqlSegment} 就可以调用QueryWrapper对象的条件sql
<select id="selectCustExtraInfoPage">SELECT * FROM cust_info ${ew.customSqlSegment}
</select>
分析
就上面就可以直接用了,下面来分析怎么调用的
首先${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 条件,不拼接 customSqlSegmentif (normal.isEmpty()) {return sqlSegment;}return "WHERE " + sqlSegment;}}return "";}
注意这里,如果没有where 条件,不拼接 customSqlSegment
所以猜测是直接调用的这个get方法,根据我们传入的属性名称,拼接成get+属性名称,这样来调用的吗?
扩展
所以我就写了一个demo
1、没有属性值
2、但是有一个getUserForId()的方法,
3、在mapper.xml文件中 :对象.userForId 但是注意:我的类里面并没有这个属性
试一下是否可以成功查询出数据!
实体类:
xml文件
调用:居然真的查询成功了!!!!!!!!!!!!!
end 查看源码中:
Reflector类中:
构造方法里面,根据该类名获取所有的get方法
到这里大家应该就明白了,
是根据所有的get方法,经过处理
private void addSetMethods(Method[] methods) {Map<String, List<Method>> 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<String, List<Method>> conflictingMethods, String name, Method method) {if (isValidPropertyName(name)) {List<Method> list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>());list.add(method);}}private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {String propName = entry.getKey();List<Method> 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 matchmatch = setter;break;}if (!isSetterAmbiguous) {match = pickBetterSetter(match, setter, propName);isSetterAmbiguous = match == null;}}if (match != null) {addSetMethod(propName, match);}}}
保存到:getMethods中
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} 可以调用的原因了