网站运行环境,负面口碑营销案例,装饰设计公司起名,兰溪自适应网站建设特点目录 一、先理清核心#xff1a;MyBatis 责任链 数据权限插件的结合逻辑
二、数据权限插件的完整实现#xff08;基于责任链#xff09;
步骤 1#xff1a;定义数据权限上下文#xff08;存储当前用户的权限信息#xff09;
步骤 2#xff1a;实现数据权限插件…目录一、先理清核心MyBatis 责任链 数据权限插件的结合逻辑二、数据权限插件的完整实现基于责任链步骤 1定义数据权限上下文存储当前用户的权限信息步骤 2实现数据权限插件责任链的具体处理者步骤 3配置插件将数据权限插件加入责任链步骤 4业务中设置数据权限上下文拦截前注入权限步骤 5业务 Mapper无需修改插件自动过滤三、责任链执行流程数据权限插件1. 完整链路2. 执行效果示例四、MyBatis 责任链在数据权限插件中的核心细节1. 责任链的 “传递性”2. 插件顺序的影响3. MetaObject 的核心作用4. 多租户场景的扩展责任链的灵活性五、数据权限插件的进阶优化责任链扩展1. 支持注解指定过滤规则2. 避免 SQL 改写错误兼容复杂 SQL六、总结责任链模式在数据权限插件中的价值一、先理清核心MyBatis 责任链 数据权限插件的结合逻辑数据权限是业务中典型的 “动态过滤数据” 场景如按租户、部门、角色过滤 SQL 结果MyBatis 基于责任链模式的插件机制可在 SQL 执行前动态拼接数据权限条件如WHERE dept_id ?且不侵入业务 Mapper 代码。核心思路数据权限插件作为责任链的具体处理者实现Interceptor接口拦截StatementHandler的prepare方法SQL 构建阶段解析原 SQL根据当前用户的权限动态拼接数据权限条件通过invocation.proceed()传递请求到下一个插件 / 原生逻辑完成 SQL 执行。二、数据权限插件的完整实现基于责任链步骤 1定义数据权限上下文存储当前用户的权限信息用于在插件中获取当前用户的权限范围如部门 ID、租户 IDjava运行/** * 数据权限上下文ThreadLocal 存储保证线程安全 */ public class DataPermissionContext { private static final ThreadLocalDataPermission CONTEXT new ThreadLocal(); // 设置当前用户的数据权限 public static void set(DataPermission dataPermission) { CONTEXT.set(dataPermission); } // 获取当前用户的数据权限 public static DataPermission get() { return CONTEXT.get(); } // 清理上下文避免内存泄漏 public static void clear() { CONTEXT.remove(); } /** * 数据权限实体 */ Data public static class DataPermission { private Long userId; // 当前用户ID private Long deptId; // 当前用户部门ID private Long tenantId; // 租户ID多租户场景 private boolean isAdmin; // 是否超级管理员无需过滤 } }步骤 2实现数据权限插件责任链的具体处理者核心是拦截StatementHandler的prepare方法改写 SQL 拼接数据权限条件java运行import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.util.Properties; /** * 数据权限插件责任链的具体处理者 * 拦截 StatementHandler.prepare()动态拼接数据权限条件 */ Intercepts({ Signature( type StatementHandler.class, // 拦截的核心组件 method prepare, // 拦截的方法SQL 预处理阶段 args {Connection.class, Integer.class} // 方法参数匹配重载 ) }) public class DataPermissionInterceptor implements Interceptor { // 需过滤的表名可通过配置注入 private static final String[] FILTER_TABLES {sys_user, sys_order, sys_leave}; Override public Object intercept(Invocation invocation) throws Throwable { // 1. 获取当前用户的数据权限上下文 DataPermissionContext.DataPermission permission DataPermissionContext.get(); // 超级管理员/无权限上下文 → 直接放行传递请求 if (permission null || permission.isAdmin()) { return invocation.proceed(); } // 2. 获取被拦截的 StatementHandler 对象责任链的处理目标 StatementHandler statementHandler (StatementHandler) invocation.getTarget(); MetaObject metaObject SystemMetaObject.forObject(statementHandler); // 3. 解析原 SQL 和 MappedStatement获取 SQL 相关信息 BoundSql boundSql (BoundSql) metaObject.getValue(delegate.boundSql); String originalSql boundSql.getSql().trim(); MappedStatement mappedStatement (MappedStatement) metaObject.getValue(delegate.mappedStatement); String sqlId mappedStatement.getId(); // 如com.example.mapper.UserMapper.selectList // 4. 判断是否需要拼接数据权限条件仅过滤指定表 boolean needFilter needFilterTable(originalSql); if (!needFilter) { return invocation.proceed(); // 无需过滤传递请求 } // 5. 动态拼接数据权限条件核心改写 SQL String newSql buildDataPermissionSql(originalSql, permission); // 替换 BoundSql 中的 SQL通过 MetaObject 保证反射安全 metaObject.setValue(delegate.boundSql.sql, newSql); System.out.println(【数据权限插件】原 SQL originalSql); System.out.println(【数据权限插件】改写后 SQL newSql); // 6. 传递请求到下一个插件/原生逻辑责任链核心不可中断必须 proceed return invocation.proceed(); } /** * 判断 SQL 是否包含需要过滤的表 */ private boolean needFilterTable(String sql) { for (String table : FILTER_TABLES) { if (sql.toLowerCase().contains(table.toLowerCase())) { return true; } } return false; } /** * 拼接数据权限条件按部门过滤 */ private String buildDataPermissionSql(String originalSql, DataPermissionContext.DataPermission permission) { // 处理 SELECT 语句兼容 WHERE/AND/OR 场景 if (originalSql.toUpperCase().startsWith(SELECT)) { int whereIndex originalSql.toUpperCase().indexOf(WHERE); if (whereIndex 0) { // 已有 WHERE 条件拼接 AND dept_id ? return originalSql.substring(0, whereIndex 5) dept_id permission.getDeptId() AND originalSql.substring(whereIndex 5); } else { // 无 WHERE 条件新增 WHERE dept_id ? return originalSql WHERE dept_id permission.getDeptId(); } } // 处理 UPDATE/DELETE 语句同理 else if (originalSql.toUpperCase().startsWith(UPDATE) || originalSql.toUpperCase().startsWith(DELETE)) { int whereIndex originalSql.toUpperCase().indexOf(WHERE); if (whereIndex 0) { return originalSql.substring(0, whereIndex 5) dept_id permission.getDeptId() AND originalSql.substring(whereIndex 5); } else { return originalSql WHERE dept_id permission.getDeptId(); } } return originalSql; } /** * 生成代理对象将插件加入责任链 */ Override public Object plugin(Object target) { // Plugin.wrapMyBatis 责任链管理器嵌套构建代理链 return Plugin.wrap(target, this); } /** * 加载插件配置如过滤表名可从配置文件读取 */ Override public void setProperties(Properties properties) { // 示例从配置读取需过滤的表名 String tables properties.getProperty(filterTables); if (tables ! null !tables.isEmpty()) { FILTER_TABLES tables.split(,); } } }步骤 3配置插件将数据权限插件加入责任链在mybatis-config.xml中注册插件指定执行顺序若有其他插件需注意顺序xmlconfiguration plugins !-- 数据权限插件责任链的具体处理者 -- plugin interceptorcom.example.plugin.DataPermissionInterceptor !-- 配置需过滤的表名可选 -- property namefilterTables valuesys_user,sys_order,sys_leave/ /plugin !-- 其他插件如日志插件、分页插件→ 按顺序加入责任链 -- plugin interceptorcom.example.plugin.SqlLogInterceptor/ /plugins /configuration步骤 4业务中设置数据权限上下文拦截前注入权限在请求入口如 Spring MVC 拦截器 / 过滤器设置当前用户的数据权限java运行/** * Spring MVC 拦截器请求到达 Controller 前设置数据权限上下文 */ Component public class DataPermissionContextInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 模拟从 Token/会话获取当前用户信息实际从登录态获取 Long userId Long.parseLong(request.getHeader(userId)); Long deptId Long.parseLong(request.getHeader(deptId)); boolean isAdmin 1.equals(request.getHeader(isAdmin)); // 设置数据权限上下文 DataPermissionContext.DataPermission permission new DataPermissionContext.DataPermission(); permission.setUserId(userId); permission.setDeptId(deptId); permission.setAdmin(isAdmin); DataPermissionContext.set(permission); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 清理上下文避免 ThreadLocal 内存泄漏 DataPermissionContext.clear(); } }步骤 5业务 Mapper无需修改插件自动过滤xml!-- SysUserMapper.xml -- select idselectUserList resultTypecom.example.entity.SysUser SELECT * FROM sys_user /select三、责任链执行流程数据权限插件1. 完整链路plaintext客户端请求 → Spring MVC 拦截器设置数据权限上下文 → MyBatis 执行 selectUserList() → 创建 StatementHandler 对象 → Plugin 类为其生成代理嵌套数据权限插件日志插件→ 调用 StatementHandler.prepare() → 触发责任链执行 ① 数据权限插件.intercept() → - 获取权限上下文 → - 解析 SQL → - 拼接 dept_id 条件 → - 调用 invocation.proceed() → ② 日志插件.intercept() → - 打印改写后的 SQL → - 调用 invocation.proceed() → 执行原生 StatementHandler.prepare() → 执行 SQL自动过滤当前部门数据→ Spring MVC 拦截器清理数据权限上下文2. 执行效果示例假设当前用户 dept_id 10原 SQLsqlSELECT * FROM sys_user插件改写后 SQLsqlSELECT * FROM sys_user WHERE dept_id 10若原 SQL 已有 WHERE 条件如SELECT * FROM sys_user WHERE status 1改写后sqlSELECT * FROM sys_user WHERE dept_id 10 AND status 1四、MyBatis 责任链在数据权限插件中的核心细节1. 责任链的 “传递性”数据权限插件必须调用invocation.proceed()否则会中断责任链原生 SQL 逻辑无法执行—— 这是 MyBatis 纯责任链的特点区别于 Spring MVC 可中断的不纯责任链。2. 插件顺序的影响若同时配置了分页插件和数据权限插件需保证xml!-- 正确顺序数据权限插件先执行拼接条件→ 分页插件后执行基于过滤后的 SQL 分页 -- plugins plugin interceptorcom.example.plugin.DataPermissionInterceptor/ plugin interceptorcom.github.pagehelper.PageInterceptor/ /plugins若顺序相反分页插件会先基于原 SQL 分页再拼接数据权限条件导致分页结果错误。3. MetaObject 的核心作用MyBatis 内部属性如BoundSql.sql是私有的插件中必须通过SystemMetaObject.forObject()获取MetaObject而非直接反射 —— 这是 MyBatis 责任链中 “安全修改目标对象” 的关键。4. 多租户场景的扩展责任链的灵活性只需修改buildDataPermissionSql方法即可适配多租户数据权限java运行// 多租户 部门双重过滤 private String buildDataPermissionSql(String originalSql, DataPermissionContext.DataPermission permission) { String tenantCondition tenant_id permission.getTenantId(); String deptCondition dept_id permission.getDeptId(); if (originalSql.toUpperCase().indexOf(WHERE) 0) { return originalSql.replaceFirst(WHERE, WHERE tenantCondition AND deptCondition AND); } else { return originalSql WHERE tenantCondition AND deptCondition; } }五、数据权限插件的进阶优化责任链扩展1. 支持注解指定过滤规则在 Mapper 方法上添加注解精细化控制是否过滤java运行Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface DataPermission { boolean enable() default true; // 是否启用数据权限 String tableAlias() default ; // 表别名避免字段冲突 } // Mapper 中使用 public interface SysUserMapper { DataPermission(enable true, tableAlias u) ListSysUser selectUserList(); } // 插件中解析注解 MappedStatement mappedStatement (MappedStatement) metaObject.getValue(delegate.mappedStatement); String mapperId mappedStatement.getId(); Class? mapperClass Class.forName(mapperId.substring(0, mapperId.lastIndexOf(.))); String methodName mapperId.substring(mapperId.lastIndexOf(.) 1); Method method mapperClass.getMethod(methodName); DataPermission annotation method.getAnnotation(DataPermission.class); if (annotation null || !annotation.enable()) { return invocation.proceed(); // 注解禁用直接放行 }2. 避免 SQL 改写错误兼容复杂 SQL针对包含GROUP BY/ORDER BY/JOIN的复杂 SQL可通过 SQL 解析器如 JSqlParser改写而非字符串拼接java运行// 引入 JSqlParser 依赖 dependency groupIdcom.github.jsqlparser/groupId artifactIdjsqlparser/artifactId version4.6/version /dependency // 插件中解析 SQL private String buildDataPermissionSql(String originalSql, DataPermissionContext.DataPermission permission) throws JSQLParserException { Select select (Select) CCJSqlParserUtil.parse(originalSql); PlainSelect plainSelect (PlainSelect) select.getSelectBody(); // 构建数据权限条件表达式 Expression condition new EqualsTo(); ((EqualsTo) condition).setLeftExpression(new Column(dept_id)); ((EqualsTo) condition).setRightExpression(new LongValue(permission.getDeptId())); // 拼接条件 if (plainSelect.getWhere() null) { plainSelect.setWhere(condition); } else { plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), condition)); } return select.toString(); }六、总结责任链模式在数据权限插件中的价值解耦性数据权限逻辑与业务 Mapper 完全解耦无需在每个 SQL 中手写dept_id ?符合 “开闭原则”灵活性新增 / 修改数据权限规则如按角色过滤只需修改插件逻辑无需改动上千个 Mapper可扩展插件可作为责任链的一环与分页、日志等插件共存互不干扰无侵入基于 MyBatis 插件机制责任链无需修改框架源码适配所有 MyBatis 版本。MyBatis 的责任链模式让数据权限插件成为 “即插即用” 的组件是业务中解决 “统一数据权限控制” 的最优方案之一。