mybatis plus数据权限插件在项目中的使用方式

 更新时间:2026年01月08日 10:27:06   作者:jpq+  
本文介绍了如何在MyBatis中实现数据权限拦截器,通过自定义注解和拦截器,动态地在SQL中添加数据权限条件

前言

平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求。实现方案有两种,一是在开发初期就做好判断,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式。在mybatis执行sql前修改语句,限定where范围。

当然拦截器生效后是全局性的,如何保证只对需要的接口进行拦截和转化,就可以应用注解进行识别

因此具体需要哪些步骤就明确了:

  1. 创建注解类
  2. 创建处理类,获取数据权限 SQL 片段,设置条件
  3. 将拦截器加到MyBatis-Plus插件中

数据权限插件

DataPermissionInterceptor

插件原理和租户插件类似动态拦截执行 SQl 然后拼接权限部分 SQL片段 , 该插件一直是免费开源的,企业高级特性-数据范围功能也是基于该原理实现,只不过添加了注解支持。

使用DataPermissionHandler

/**
 * 数据权限处理器
 *
 * @author hubin
 * @since 3.4.1 +
 */
public interface DataPermissionHandler {

    /**
     * 获取数据权限 SQL 片段
     *
     * @param where             待执行 SQL Where 条件表达式
     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
     * @return JSqlParser 条件表达式
     */
    Expression getSqlSegment(Expression where, String mappedStatementId);
}

先定义一个注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
    /**
     * 表前缀
     */
    String tableAlis() default "";
    /**
     * 机构前缀
     */
    String orgAlis() default "org_id";

    /**
     * 是否生效
     */
    boolean enabled() default true;
}

新建一个OrgScopeHandler,通过实现以上接口:

 @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {

        try {
            Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
            String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                DataScope annotation = method.getAnnotation(DataScope.class);
                if (ObjectUtils.isNotEmpty(annotation)
                        && (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
                    if (annotation.enabled()) {

                        return dataScopeFilter(annotation, where);
                    }

                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return where;
    }

在项目中的MybatisPlusConfig中加上自定义的OrgScopeHandler:

 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 // 数据权限
        interceptor.addInnerInterceptor(new DataPermissionInterceptor(new OrgScopeHandler()));

getSqlSegment中的实现是遍历Mybatis MappedStatement类的所有方法,对每一个方法执行以下操作:

获取该方法上的 DataScope 注解。

检查注解是否存在且不为空(ObjectUtils.isNotEmpty(annotation))。

检查方法名是否与之前获取的 methodName 相同,或者是方法名后加上 _COUNT 后缀的情况。

如果以上条件都满足,再检查 DataScope 注解的 enabled() 属性是否为 true。

如果找到了匹配的方法并且注解启用,那么调用 dataScopeFilter 方法,传入该方法上的 DataScope 注解和 where 参数,进行数据范围过滤。

自定义的dataScopeFilter方法

    /**
     * 根据注解中的内容构造 拼接sql,用于数据权限过滤
     *
     * @param dataPermission 数据权限注解,用于指定表和组织别名等信息
     * @param where 原始sql的where条件表达式
     * @return 返回拼接后的sql表达式,添加了数据权限的过滤条件
     */
    private Expression dataScopeFilter(DataScope dataPermission, Expression where) {
        // 获取当前登录的用户
        NutUser user = SecurityUtil.getNutUser();

        String tableAlias = dataPermission.tableAlis(); // 表别名
        String orgAlias = dataPermission.orgAlis(); // 组织别名
        // 构造数据过滤的in表达式
        InExpression inExpression = new InExpression();
        // 设置左边的字段表达式,根据表和组织别名构建
        inExpression.setLeftExpression(buildColumn(tableAlias, orgAlias));
        
        // 使用JSQLParser解析组织ID的查询语句
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Statement statement;
        try {
            // 构造查询当前用户所属组织及其子组织ID的sql语句
            StringReader statementReader = new StringReader(
                    "select id from sys_org where path like '%" + user.getOrgId() + "%'");
            statement = parserManager.parse(statementReader);
        } catch (JSQLParserException e) {
            // 处理解析异常
            throw new RuntimeException(e);
        }
        Select selectStatement = (Select) statement;
        SubSelect subSelect = new SubSelect();
        subSelect.setSelectBody(selectStatement.getSelectBody()); // 设置子查询的查询体
        inExpression.setRightExpression(subSelect); // 设置右边的子查询表达式

        // 如果原有where条件不为空,则将数据权限过滤条件和原有条件用and连接
        return ObjectUtils.isNotEmpty(where) ? new AndExpression(where, new Parenthesis(inExpression)) : inExpression;
    }

buildColumn方法:

 /**
     * 构建Column
     *
     * @param tableAlias 表别名
     * @param columnName 字段名称
     * @return 带表别名字段
     */
    public static Column buildColumn(String tableAlias, String columnName) {
        if (StringUtils.isNotEmpty(tableAlias)) {
            columnName = tableAlias + "." + columnName;
        }
        return new Column(columnName);
    }

使用:

  /**
     * 分页查询
     *
     * @param page  page
     * @param query query
     */
    @DataScope(tableAlis = "item")
    Page<InfoBO> selectPageQuery(Page page, @Param("query") HandleQuery query);

结果是实际的sql上将拼接上:

and (org_id IN (SELECT id FROM sys_org WHERE path LIKE concat('%', 1, '%')))

CCJSqlParserManager类解析

CCJSqlParserManager 类是 JSqlParser 库中的一个关键类,它主要用于管理和执行 SQL 语句的解析操作。

JSqlParser 是一个 Java 库,用于解析 SQL 语言,并将其转化为可遍历、操作和修改的 Java 对象模型。

CCJSqlParserManager 主要功能如下:

  1. 初始化解析器资源:该类负责初始化和管理 JSqlParser 解析器所需的资源。
  2. 解析 SQL 语句:它可以接受字符串形式的 SQL 查询,并通过 parse() 方法将其转换成 Statement 对象,这是一个抽象类,代表了 SQL 语句的不同类型(如 Select, Update, Delete, Insert 等)。
  3. 处理并发请求:CCJSqlParserManager 可能会实现资源池或其他机制来更有效地处理多个并发的 SQL 解析请求。
    例如,在上述代码片段中:
CCJSqlParserManager parserManager = new CCJSqlParserManager();
Statement statement;
try {
    StringReader statementReader = new StringReader("select id from sys_org where path like '%" + user.getOrgId() + "%'");
    statement = parserManager.parse(statementReader);
} catch (JSQLParserException e) {
    throw new RuntimeException(e);
}

这段代码就是创建了一个 CCJSqlParserManager 实例,然后使用它来解析一个 SQL 语句,这个 SQL 语句是为了查询当前用户及其子组织的 ID。

通过解析得到的 statement 对象可以进一步被分析或修改,以便进行动态 SQL 生成、权限检查、SQL 优化等各种操作。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • IDEA2020.2.3中创建JavaWeb工程的完整步骤记录

    IDEA2020.2.3中创建JavaWeb工程的完整步骤记录

    这篇文章主要给大家介绍了关于IDEA2020.2.3中创建JavaWeb工程的完整步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • java创建jar包并被项目引用步骤详解

    java创建jar包并被项目引用步骤详解

    这篇文章主要介绍了java创建jar包并被项目引用步骤详解,jar包实现了特定功能的,java字节码文件的压缩包,更多相关内容需要的朋友可以参考一下
    2022-07-07
  • java的多线程用法编程总结

    java的多线程用法编程总结

    本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
    2016-10-10
  • 使用JMF实现java视频播放器

    使用JMF实现java视频播放器

    这篇文章主要为大家详细介绍了使用JMF实现java视频播放器的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • mybatis中的test语句失效处理方式

    mybatis中的test语句失效处理方式

    这篇文章主要介绍了mybatis中的test语句失效处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • SpringCloud Gateway使用redis实现动态路由的方法

    SpringCloud Gateway使用redis实现动态路由的方法

    这篇文章主要介绍了SpringCloud Gateway使用redis实现动态路由的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Spring IoC容器常见获取Bean的方式汇总示例解析

    Spring IoC容器常见获取Bean的方式汇总示例解析

    这篇文章主要为大家介绍了Spring IoC容器常见获取Bean的方式汇总示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Java设计模式之模版方法模式简介

    Java设计模式之模版方法模式简介

    这篇文章主要介绍了Java设计模式之模版方法模式,需要的朋友可以参考下
    2014-07-07
  • 养成良好java代码编码规范

    养成良好java代码编码规范

    这篇文章主要介绍了如何养成良好java代码编码规范,规范需要平时编码过程中注意,是一个慢慢养成的好习惯,下面小编就带大家来一起详细了解一下吧
    2019-06-06
  • Java轻松实现在Word中插入页眉页脚的实用指南

    Java轻松实现在Word中插入页眉页脚的实用指南

    在现代企业应用中,Java 开发者经常需要处理各种文档操作,本文将深入探讨如何利用功能强大的 Spire.Doc for Java 库轻松实现各种页眉页脚的插入需求,需要的小伙伴可以了解下
    2025-09-09

最新评论