Implement Role‑Based Data Permissions in MyBatis‑Plus with Custom Annotations

This article explains how to add data‑permission filtering to MyBatis‑Plus by creating a custom annotation, implementing a global interceptor, wiring a permission‑handler, configuring the interceptor in the MyBatis‑Plus plugin, and extending the solution with role‑based scopes for advanced use cases.

Architect
Architect
Architect
Implement Role‑Based Data Permissions in MyBatis‑Plus with Custom Annotations

Basic version

Two ways to limit data based on the current user's role: add checks in each service method or use a global interceptor that modifies the SQL before execution.

Custom annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserDataPermission {
}

Interceptor

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class MyDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
    private MyDataPermissionHandler dataPermissionHandler;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            this.setWhere((PlainSelect) selectBody, (String) obj);
        } else if (selectBody instanceof SetOperationList) {
            SetOperationList setOperationList = (SetOperationList) selectBody;
            List<SelectBody> selectBodyList = setOperationList.getSelects();
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }

    private void setWhere(PlainSelect plainSelect, String whereSegment) {
        Expression sqlSegment = this.dataPermissionHandler.getSqlSegment(plainSelect, whereSegment);
        if (sqlSegment != null) {
            plainSelect.setWhere(sqlSegment);
        }
    }
}

Interceptor handler

@Slf4j
public class MyDataPermissionHandler {
    private RemoteRoleService remoteRoleService;
    private RemoteUserService remoteUserService;

    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        remoteRoleService = SpringUtil.getBean(RemoteRoleService.class);
        remoteUserService = SpringUtil.getBean(RemoteUserService.class);
        Expression where = plainSelect.getWhere();
        if (where == null) {
            where = new HexValue(" 1 = 1 ");
        }
        log.info("开始进行权限过滤,where: {},mappedStatementId: {}", where, whereSegment);
        String className = whereSegment.substring(0, whereSegment.lastIndexOf('.'));
        String methodName = whereSegment.substring(whereSegment.lastIndexOf('.') + 1);
        Table fromItem = (Table) plainSelect.getFromItem();
        Alias fromItemAlias = fromItem.getAlias();
        String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName();
        Method[] methods = Class.forName(className).getMethods();
        for (Method m : methods) {
            if (Objects.equals(m.getName(), methodName)) {
                UserDataPermission annotation = m.getAnnotation(UserDataPermission.class);
                if (annotation == null) {
                    return where;
                }
                User user = SecurityUtils.getUser();
                EqualsTo equalsTo = new EqualsTo();
                equalsTo.setLeftExpression(new Column(mainTableName + ".creator_code"));
                equalsTo.setRightExpression(new StringValue(user.getUserCode()));
                return new AndExpression(where, equalsTo);
            }
        }
        where = new HexValue(" 1 = 2 ");
        return where;
    }
}

Register the interceptor in the MyBatis‑Plus plugin:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    MyDataPermissionInterceptor dataPermissionInterceptor = new MyDataPermissionInterceptor();
    dataPermissionInterceptor.setDataPermissionHandler(new MyDataPermissionHandler());
    interceptor.addInnerInterceptor(dataPermissionInterceptor);
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

Use the annotation on mapper methods:

@UserDataPermission
List<CustomerAllVO> selectAllCustomerPage(IPage<CustomerAllVO> page, @Param("customerName") String customerName);

Advanced version

Introduce role‑based scopes (ALL, DEPT, MYSELF) and map them to business roles.

Create a DataScope enum.

Create a DataPermission enum that links roles to scopes.

Rewrite the handler to apply the appropriate scope.

DataScope enum

@AllArgsConstructor
@Getter
public enum DataScope {
    ALL("ALL"),
    DEPT("DEPT"),
    MYSELF("MYSELF");
    private String name;
}

DataPermission enum

@AllArgsConstructor
@Getter
public enum DataPermission {
    DATA_MANAGER("数据管理员", "DATA_MANAGER", DataScope.ALL),
    DATA_AUDITOR("数据审核员", "DATA_AUDITOR", DataScope.DEPT),
    DATA_OPERATOR("数据业务员", "DATA_OPERATOR", DataScope.MYSELF);
    private String name;
    private String code;
    private DataScope scope;
    // utility methods omitted for brevity
}

Handler with role logic

@Slf4j
public class MyDataPermissionHandler {
    private RemoteRoleService remoteRoleService;
    private RemoteUserService remoteUserService;

    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        remoteRoleService = SpringUtil.getBean(RemoteRoleService.class);
        remoteUserService = SpringUtil.getBean(RemoteUserService.class);
        Expression where = plainSelect.getWhere();
        if (where == null) {
            where = new HexValue(" 1 = 1 ");
        }
        log.info("开始进行权限过滤,where: {},mappedStatementId: {}", where, whereSegment);
        String className = whereSegment.substring(0, whereSegment.lastIndexOf('.'));
        String methodName = whereSegment.substring(whereSegment.lastIndexOf('.') + 1);
        Table fromItem = (Table) plainSelect.getFromItem();
        Alias fromItemAlias = fromItem.getAlias();
        String mainTableName = fromItemAlias == null ? fromItem.getName() : fromItemAlias.getName();
        Method[] methods = Class.forName(className).getMethods();
        for (Method m : methods) {
            if (Objects.equals(m.getName(), methodName)) {
                UserDataPermission annotation = m.getAnnotation(UserDataPermission.class);
                if (annotation == null) {
                    return where;
                }
                User user = SecurityUtils.getUser();
                Set<String> roleTypeSet = remoteRoleService.currentUserRoleType();
                DataScope scopeType = DataPermission.getScope(roleTypeSet);
                switch (scopeType) {
                    case ALL:
                        return where;
                    case DEPT:
                        List<String> deptUserList = remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
                        ItemsList deptList = new ExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
                        InExpression inExpr = new InExpression(new Column(mainTableName + ".creator_code"), deptList);
                        return new AndExpression(where, inExpr);
                    case MYSELF:
                        EqualsTo eq = new EqualsTo();
                        eq.setLeftExpression(new Column(mainTableName + ".creator_code"));
                        eq.setRightExpression(new StringValue(user.getUserCode()));
                        return new AndExpression(where, eq);
                    default:
                        break;
                }
            }
        }
        where = new HexValue(" 1 = 2 ");
        return where;
    }
}

Key reminders: add the interceptor to the MyBatis‑Plus plugin, ensure the data‑filter field (e.g., creator_code) exists, and adjust the scope logic according to your business roles.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaInterceptormybatis-plusData Permission
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.