Backend Development 13 min read

MyBatis-Plus Data Permission Interceptor: Annotation, Interceptor, and Role-Based Scope Implementation

This guide explains how to implement data permission control in MyBatis-Plus by creating a custom annotation, an interceptor that modifies SQL before execution, a handler that builds WHERE clauses based on user roles, and how to register the interceptor in the MyBatis-Plus plugin, covering both basic and advanced versions.

Architecture Digest
Architecture Digest
Architecture Digest
MyBatis-Plus Data Permission Interceptor: Annotation, Interceptor, and Role-Based Scope Implementation

In many backend projects, data access must be limited according to the current user's role. When the requirement appears mid‑project or you prefer a centralized solution, using a MyBatis interceptor to rewrite SQL statements is an effective approach.

The solution consists of four main steps:

Create a custom annotation to mark methods that require data permission filtering.

Implement an InnerInterceptor (named MyDataPermissionInterceptor ) that intercepts queries before they are executed.

Write a handler ( MyDataPermissionHandler ) that generates the appropriate WHERE clause based on the logged‑in user and, in the advanced version, on the user's role.

Add the interceptor to the MyBatis‑Plus plugin configuration.

Custom annotation

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

Interceptor implementation

@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;
        }
        PluginUtils.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
selectBodyList = setOperationList.getSelects();
            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
        }
    }

    /**
     * Set the WHERE condition generated by the handler.
     */
    private void setWhere(PlainSelect plainSelect, String whereSegment) {
        Expression sqlSegment = this.dataPermissionHandler.getSqlSegment(plainSelect, whereSegment);
        if (null != sqlSegment) {
            plainSelect.setWhere(sqlSegment);
        }
    }
}

Basic handler (single‑role, = expression)

@Slf4j
public class MyDataPermissionHandler {

    private MyDataPermissionHandler dataPermissionHandler;

    /**
     * Build the WHERE clause for data permission.
     */
    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        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 usesEqualsTo = new EqualsTo();
                usesEqualsTo.setLeftExpression(new Column(mainTableName + ".creator_code"));
                usesEqualsTo.setRightExpression(new StringValue(user.getUserCode()));
                return new AndExpression(where, usesEqualsTo);
            }
        }
        where = new HexValue(" 1 = 2 ");
        return where;
    }
}

Registering the interceptor

@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;
}

If the project already defines a MybatisPlusInterceptor bean, simply insert the custom interceptor into its list to avoid breaking existing configuration.

Advanced version – role‑based scope

Define enums for data scope and role mapping:

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

@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;
    // static helper methods omitted for brevity
}

Enhanced handler that fetches the current user's roles and applies the appropriate scope:

@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 ");
        }
        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
roleTypeSet = remoteRoleService.currentUserRoleType();
                DataScope scopeType = DataPermission.getScope(roleTypeSet);
                switch (scopeType) {
                    case ALL:
                        return where;
                    case DEPT:
                        List
deptUserList = remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
                        ItemsList deptList = new ExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
                        InExpression inExpression = new InExpression(new Column(mainTableName + ".creator_code"), deptList);
                        return new AndExpression(where, inExpression);
                    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;
    }
}

Usage: simply annotate the desired mapper methods with @UserDataPermission , for example:

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

Key take‑aways:

Register the interceptor in the MyBatis‑Plus plugin to make it effective globally.

Use a dedicated column (e.g., creator_code or dept_code ) as the data‑filtering field.

Advanced version allows role‑based scope (ALL, DEPT, MYSELF) without modifying existing mapper SQL.

backendJavaInterceptorMyBatis-PlusData Permissionrole-based access
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login 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.