Implement Role‑Based Data Permissions in MyBatis‑Plus with Custom Interceptors
This guide explains how to enforce user‑role based data access in MyBatis‑Plus by creating a custom annotation, building an InnerInterceptor that modifies SQL WHERE clauses, configuring the interceptor in the MyBatis‑Plus plugin, and extending the approach to handle department‑level and self‑only scopes.
In many Java projects, data access must be limited to the range permitted by the current user's role. This article shows two ways to achieve that in MyBatis‑Plus: adding explicit checks in the service layer or, more flexibly, using a MyBatis interceptor that rewrites the SQL before execution.
Basic Implementation
Create a custom annotation @UserDataPermission.
Implement an interceptor class that extends JsqlParserSupport and implements InnerInterceptor, overriding beforeQuery and processSelect to inject a WHERE clause.
Write a handler class that builds the SQL fragment based on the current user (e.g., creator_code = ?).
Register the interceptor in a MybatisPlusInterceptor bean.
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 {} 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 setOperationList = (SetOperationList) selectBody;
setOperationList.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);
}
}
} 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 ");
}
// obtain mapper class and method name
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);
}
}
// no permission
return new HexValue(" 1 = 2 ");
}
} @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;
}Mapper methods that need data filtering are simply annotated:
@UserDataPermission
List<CustomerAllVO> selectAllCustomerPage(IPage<CustomerAllVO> page, @Param("customerName") String customerName);Advanced Implementation (Role‑Based Scopes)
The advanced version introduces two enums to represent data scopes and role‑to‑scope mapping, and the handler is rewritten to consider the current user's roles.
public enum DataScope {
ALL("ALL"),
DEPT("DEPT"),
MYSELF("MYSELF");
private String name;
DataScope(String name) { this.name = name; }
} 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 helpers omitted for brevity
} 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 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();
Set<String> roleTypes = remoteRoleService.currentUserRoleType();
DataScope scope = DataPermission.getScope(roleTypes);
switch (scope) {
case ALL:
return where;
case DEPT:
List<String> deptUsers = remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
ItemsList deptList = new ExpressionList(deptUsers.stream()
.map(StringValue::new).collect(Collectors.toList()));
InExpression inExpr = new InExpression(new Column(mainTable + ".creator_code"), deptList);
return new AndExpression(where, inExpr);
case MYSELF:
EqualsTo eq = new EqualsTo();
eq.setLeftExpression(new Column(mainTable + ".creator_code"));
eq.setRightExpression(new StringValue(user.getUserCode()));
return new AndExpression(where, eq);
default:
break;
}
}
}
return new HexValue(" 1 = 2 ");
}
}Key points to remember:
Register the custom interceptor in the MyBatis‑Plus plugin so it takes effect.
The data‑filtering field in the example is creator_code; it can be replaced with any column that identifies ownership, such as dept_code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
