Implementing Data Permission Interceptor in MyBatis‑Plus Using Custom Annotations
This article explains how to create a custom annotation and MyBatis‑Plus interceptor to enforce data‑permission filtering based on user roles, provides both basic and advanced implementations with complete Java code examples, and shows how to integrate the interceptor into the MyBatis‑Plus plugin configuration.
In many backend projects, data access must be limited to the range permitted by the current user's role. Two approaches exist: embed the filter logic in each query or use a global interceptor that modifies the SQL before execution. The interceptor method is especially useful for requirements added later or when you want to avoid repetitive code.
The solution consists of four steps:
Create a custom annotation ( @UserDataPermission ).
Implement an InnerInterceptor (e.g., MyDataPermissionInterceptor ) that overrides beforeQuery and processes the Select statement.
Write a handler class ( MyDataPermissionHandler ) that builds the WHERE clause based on the logged‑in user and their role.
Register the interceptor in the MybatisPlusInterceptor bean.
Basic Implementation
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 "); }
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 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();
EqualsTo eq = new EqualsTo();
eq.setLeftExpression(new Column(mainTable + ".creator_code"));
eq.setRightExpression(new StringValue(user.getUserCode()));
return new AndExpression(where, eq);
}
}
return new HexValue(" 1 = 2 ");
}
}Register 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;
}Advanced Version
The advanced implementation adds role‑based scope handling (ALL, DEPT, MYSELF) and supports non‑mapper custom SQL by retrieving role information from remote services and constructing appropriate IN or equality expressions.
public enum DataScope { ALL("ALL"), DEPT("DEPT"), MYSELF("MYSELF"); private String name; }
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 */ }The handler now obtains the current user's roles, determines the scope, and builds the WHERE clause accordingly (returning all rows, department rows via IN , or only the user's rows).
Finally, apply the @UserDataPermission annotation on mapper methods (or on a middle‑layer interface that extends BaseMapper ) to activate the filter without changing existing business code.
Remember to add the interceptor to the MyBatis‑Plus plugin configuration, ensure the creator_code (or another business field) exists in your tables, and adjust the scope logic to match your application's role model.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.