Backend Development 16 min read

Implementing a Data Permission Interceptor in MyBatis‑Plus Using Annotations

This article explains how to create a custom annotation and a MyBatis‑Plus interceptor that automatically injects data‑permission WHERE clauses into SQL statements, supports role‑based scopes, and shows both basic and advanced implementations with full Java code examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Implementing a Data Permission Interceptor in MyBatis‑Plus Using Annotations

In many Java back‑end projects you need to limit the data a user can see according to his role; this can be done either by adding explicit checks in each service method or, more conveniently, by using a MyBatis‑Plus interceptor that rewrites the SQL before it is executed.

The interceptor works globally, so to apply it only to the required methods we define a custom annotation and let the interceptor recognize that annotation at runtime.

The required steps are:

Create the custom annotation class.

Implement an InnerInterceptor (extending JsqlParserSupport ) that overrides beforeQuery and processes the SELECT statement.

Write a handler that builds the appropriate WHERE fragment based on the current user and, in the advanced version, on the user’s role.

Register the interceptor in the MybatisPlusInterceptor bean.

Basic code example

Custom annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

Interceptor

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.*;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.select.*;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.SQLException;
import java.util.List;

@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;
            setOperationList.getSelects().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);
        }
    }
}

Handler (basic version)

import cn.hutool.core.collection.CollectionUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class MyDataPermissionHandler {
    @SneakyThrows(Exception.class)
    public Expression getSqlSegment(PlainSelect plainSelect, String whereSegment) {
        Expression where = plainSelect.getWhere();
        if (where == null) {
            where = new HexValue(" 1 = 1 ");
        }
        log.info("Start permission filter, where: {}, mappedStatementId: {}", where, whereSegment);
        String className = whereSegment.substring(0, whereSegment.lastIndexOf('.'));
        String methodName = whereSegment.substring(whereSegment.lastIndexOf('.') + 1);
        Table fromItem = (Table) plainSelect.getFromItem();
        String mainTableName = fromItem.getAlias() == null ? fromItem.getName() : fromItem.getAlias().getName();
        for (Method m : Class.forName(className).getMethods()) {
            if (Objects.equals(m.getName(), methodName)) {
                UserDataPermission annotation = m.getAnnotation(UserDataPermission.class);
                if (annotation == null) {
                    return where;
                }
                User user = SecurityUtils.getUser();
                EqualsTo eq = new EqualsTo();
                eq.setLeftExpression(new Column(mainTableName + ".creator_code"));
                eq.setRightExpression(new StringValue(user.getUserCode()));
                return new AndExpression(where, eq);
            }
        }
        // No permission
        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;
}

When the project already defines a MybatisPlusInterceptor bean, simply add the custom interceptor to its list to avoid breaking existing configuration.

Usage in mapper interfaces

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

Advanced version – role‑based data scope

The advanced implementation adds a role enum ( DataPermission ) and a scope enum ( DataScope ) so that administrators can see all data, department managers see their department’s data, and ordinary users see only their own records.

Enums:

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

The handler now obtains the current user’s role set from remote services and builds the WHERE clause according to the resolved DataScope (ALL, DEPT, MYSELF).

// Inside getSqlSegment(...)
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 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;
}

A DataPermissionMapper interface extends BaseMapper and adds the @UserDataPermission annotation to all CRUD methods, allowing you to keep the original mapper unchanged while enforcing permission checks.

public interface DataPermissionMapper
extends BaseMapper
{
    @Override
    @UserDataPermission
    T selectById(Serializable id);
    // other CRUD methods annotated similarly …
}

Key points to remember:

Make sure the interceptor is added to the MyBatis‑Plus plugin configuration.

The data‑filtering field (e.g., creator_code ) must exist in the target tables; you can replace it with other fields such as dept_code if needed.

By following these steps you can centrally enforce row‑level data permissions in a MyBatis‑Plus based back‑end application.

backendJavaSQLInterceptorMyBatis-PlusAnnotationData Permission
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.