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