目录
使用方法:
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 条件,不拼接 customSqlSegment
if (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 match
match = 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} 可以调用的原因了
版权归原作者 令狐少侠2011 所有, 如有侵权,请联系我们删除。