若依DataScopeAspect数据权限解析和ew.customSqlSegment源码解析

本文围绕MyBatis-Plus在自定义SQL语句中调用QueryWrapper查询条件展开。介绍了ew.customSqlSegment的使用,判断是否有where条件来拼接,还说明了在接口和Mapper.xml文件中的使用方法。通过分析和扩展实验,揭示了其调用原理是根据get方法处理保存并执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


使用方法:

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} 可以调用的原因了

<think>好的,我现在需要解决用户提出的关于MyBatis Plus中`$ew.customSqlSegment`报红的问题。首先,我得理解用户的具体情况。报红通常是指IDE(比如IntelliJ IDEA)在代码中标记了红色波浪线,提示有错误或警告。用户可能在使用`${ew.customSqlSegment}`时,IDE无法识别该变量,导致语法检查错误。 首先,我应该回忆一下MyBatis Plus的相关知识。`ew`代表的是Entity Wrapper,比如`QueryWrapper`或`LambdaQueryWrapper`。`customSqlSegment`是Wrapper中的一个属性,用于生成自定义的SQL片段。在XML或注解中使用`${ew.customSqlSegment}`时,MyBatis会将其替换为对应的SQL条件。 接下来,用户的问题可能出在几个方面: 1. **变量名是否正确**:用户是否正确地使用了`ew.customSqlSegment`,有没有拼写错误,比如大小写是否一致。MyBatis中的属性是区分大小写的,所以如果写成`customsqlsegment`就会出错。 2. **参数传递是否正确**:在Mapper方法中,用户是否使用`@Param(Constants.WRAPPER)`注解将Wrapper对象命名为`ew`。例如,方法参数应该是`@Param(Constants.WRAPPER) Wrapper<T> wrapper`,这样在SQL中才能通过`ew`来引用。 3. **IDE的识别问题**:有时候IDE可能无法解析MyBatis的变量,尤其是当使用美元符号`${}`时,这可能导致误报。需要检查是否有必要的插件或设置,比如MyBatis插件是否安装,或者是否启用了合适的语言注入。 4. **MyBatis Plus版本问题**:某些旧版本可能存在bug,导致`customSqlSegment`无法正确生成,或者语法有变化。用户可能需要升级到最新版本。 然后,我应该参考用户提供的引用内容。引用[3]提到`ew.customSqlSegment`是WHERE加上SQL语句,如果没有WHERE条件则不会添加。引用[4]中的代码示例显示在注解中使用`${ew.customSqlSegment}`,并且参数使用了`@Param(Constants.WRAPPER)`,这可能是正确的用法。引用[2]讨论的是别名问题,可能与当前问题无关,但说明用户可能在复杂查询中使用该片段。 可能的原因解决方法: - **拼写错误**:检查是否正确地写成`ew.customSqlSegment`,注意大小写。例如,`customSqlSegment`中的'S''S'是大写的。 - **参数注解缺失**:确保Mapper方法的参数有`@Param(Constants.WRAPPER)`,这样MyBatis才能识别`ew`作为参数名。 - **IDE配置问题**:安装MyBatis插件(如MyBatisX)帮助IDE识别MyBatis的变量。或者,调整IDE的检查设置,忽略SQL中的变量未识别的警告。 - **语法问题**:检查SQL语句是否正确,比如是否有遗漏的引号或括号,尤其是在拼接`${ew.customSqlSegment}`的位置是否合适。 - **版本兼容性**:确认使用的MyBatis Plus版本是否支持`customSqlSegment`,查看官方文档或更新到最新版本。 另外,用户可能需要检查XML文件中的SQL是否正确,或者在使用注解的情况下,确保字符串拼接正确,没有语法错误。例如,在Java的字符串拼接中,是否正确地使用了空格,避免SQL语句连在一起导致语法错误。 总结,需要从代码拼写、参数传递、IDE设置、版本兼容性等多个方面排查问题。可能需要用户提供具体的代码片段,以便更准确地定位问题,但根据现有信息,上述几个方面是最可能的原因。</think>### 解决MyBatis Plus中`$ew.customSqlSegment`报红问题的方法 #### 原因分析 1. **变量名称拼写错误** MyBatis Plus严格区分大小写,若写成`customsqlsegment`(全小写)或`customsqlSegment`(首字母小写),会导致无法识别。正确格式应为`customSqlSegment`[^3]。 2. **缺少`@Param(Constants.WRAPPER)`注解** 在Mapper接口方法中,若未通过`@Param(Constants.WRAPPER)`指定Wrapper参数的别名为`ew`,MyBatis无法解析`${ew.customSqlSegment}`[^4]。 3. **IDE未正确识别MyBatis变量** IntelliJ IDEA等IDE可能因未安装插件或配置问题,将`${ew.customSqlSegment}`误判为“未知变量”,导致报红。 4. **MyBatis Plus版本过低** 旧版本可能存在语法兼容性问题,建议升级至最新稳定版(如3.5.3+)。 --- #### 解决方案 **1. 检查变量名称与大小写** 确保SQL中严格使用`${ew.customSqlSegment}`,注意`S``C`为大写。 示例代码: ```java @Select("SELECT * FROM user ${ew.customSqlSegment}") List<User> selectList(@Param(Constants.WRAPPER) Wrapper<User> wrapper); ``` **2. 添加`@Param(Constants.WRAPPER)`注解** 在Mapper方法参数中明确指定Wrapper别名为`ew`: ```java // 正确写法 List<User> selectByCondition(@Param(Constants.WRAPPER) Wrapper<User> wrapper); ``` **3. 配置IDE支持MyBatis语法** - **安装插件**:在IntelliJ IDEA中安装`MyBatisX`插件,增强MyBatis语法识别。 - **忽略误报**:通过`Alt + Enter`选择`Inject language or reference` → `SQL`,手动标记SQL片段以消除警告。 **4. 升级MyBatis Plus版本** 在`pom.xml`中更新依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> ``` **5. 验证SQL拼接逻辑** 确保`customSqlSegment`生成的SQL条件合法。例如: ```sql -- 正确拼接后示例 SELECT * FROM user WHERE age > 20 AND name LIKE '%John%' ``` --- #### 示例代码 **Mapper接口**: ```java public interface UserMapper extends BaseMapper<User> { @Select("SELECT id, name, age FROM user ${ew.customSqlSegment}") List<User> selectCustom(@Param(Constants.WRAPPER) Wrapper<User> wrapper); } ``` **Service调用**: ```java // 构建查询条件 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.gt("age", 20).like("name", "John"); // 执行查询 List<User> users = userMapper.selectCustom(wrapper); ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI小胖

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值