Backend Development 16 min read

Implementing Data Permission Interceptor in MyBatis‑Plus with Custom Annotations

This article explains how to create a custom annotation and a MyBatis‑Plus interceptor that automatically injects data‑permission SQL conditions based on the current user's role, showing both a basic implementation and an advanced version with role‑based scope handling, complete with code examples and integration steps.

Top Architect
Top Architect
Top Architect
Implementing Data Permission Interceptor in MyBatis‑Plus with Custom Annotations

In many projects, data access needs to be limited to the current user's permission scope. This guide shows how to implement such a restriction in MyBatis‑Plus by using a custom annotation and an interceptor that modifies the SQL WHERE clause before execution.

Basic Implementation Steps

Create a custom annotation @UserDataPermission .

Implement an interceptor class that extends JsqlParserSupport and implements InnerInterceptor , overriding beforeQuery and processSelect to inject the permission condition.

Write a handler class ( MyDataPermissionHandler ) that builds the appropriate Expression (e.g., EqualsTo for the creator code) based on the current user.

Add the interceptor to the MyBatis‑Plus plugin configuration.

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; }
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) {
setWhere((PlainSelect) selectBody, (String) obj);
} else if (selectBody instanceof SetOperationList) {
((SetOperationList) selectBody).getSelects().forEach(s -> setWhere((PlainSelect) s, (String) obj));
}
}
private void setWhere(PlainSelect plainSelect, String whereSegment) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect, whereSegment);
if (sqlSegment != null) { plainSelect.setWhere(sqlSegment); }
}
}

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 "); }
// extract mapper class and method
String className = whereSegment.substring(0, whereSegment.lastIndexOf('.'));
String methodName = whereSegment.substring(whereSegment.lastIndexOf('.') + 1);
Table fromItem = (Table) plainSelect.getFromItem();
Alias alias = fromItem.getAlias();
String mainTable = alias == null ? fromItem.getName() : alias.getName();
for (Method m : Class.forName(className).getMethods()) {
if (Objects.equals(m.getName(), methodName)) {
UserDataPermission ann = m.getAnnotation(UserDataPermission.class);
if (ann == null) { return where; }
User user = SecurityUtils.getUser();
// simple = condition on creator_code
EqualsTo eq = new EqualsTo();
eq.setLeftExpression(new Column(mainTable + ".creator_code"));
eq.setRightExpression(new StringValue(user.getUserCode()));
return new AndExpression(where, eq);
}
}
// no permission
return new HexValue(" 1 = 2 ");
}
}

Integration into MyBatis‑Plus

Add the interceptor to the MybatisPlusInterceptor bean, optionally chaining it with pagination:

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

Advanced Version – Role‑Based Scope

The advanced implementation introduces a DataScope enum (ALL, DEPT, MYSELF) and a DataPermission enum that maps roles to scopes. The handler retrieves the current user's role set, determines the scope, and builds either a simple equality, an IN expression for department users, or returns the original condition for full access.

@AllArgsConstructor
@Getter
public enum DataScope { ALL("ALL"), DEPT("DEPT"), MYSELF("MYSELF"); }
@AllArgsConstructor
@Getter
public enum DataPermission {
DATA_MANAGER("数据管理员", "DATA_MANAGER", DataScope.ALL),
DATA_AUDITOR("数据审核员", "DATA_AUDITOR", DataScope.DEPT),
DATA_OPERATOR("数据业务员", "DATA_OPERATOR", DataScope.MYSELF);
// ... getters and utility methods ...
}

In the handler, after obtaining the user and role information, a switch on the scope builds the appropriate SQL fragment:

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 in = new InExpression(new Column(mainTable + ".creator_code"), deptList);
return new AndExpression(where, in);
case MYSELF:
EqualsTo eq = new EqualsTo();
eq.setLeftExpression(new Column(mainTable + ".creator_code"));
eq.setRightExpression(new StringValue(user.getUserCode()));
return new AndExpression(where, eq);
}

Usage

Simply annotate the mapper methods that require data permission filtering with @UserDataPermission :

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

When the application runs, the interceptor will automatically add the appropriate WHERE clause based on the current user's role, without modifying each service or controller.

Key Points

Register the interceptor in the MyBatis‑Plus plugin to ensure it takes effect globally.

Use a dedicated field such as creator_code (or dept_code ) as the permission marker.

The advanced version supports role‑based scope (all data, department data, or own data) and can be extended further.

Javabackend developmentInterceptorMyBatis-PlusData Permission
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.