当前位置: 首页 > news >正文

MyBatis 拦截器让搞定监控、脱敏和权限控制

一、先搞懂:MyBatis 拦截器到底能干嘛?

MyBatis 的拦截器本质是JDK 动态代理,能在 SQL 执行的关键节点 “插队” 执行我们的逻辑。它能拦截 4 个核心组件,覆盖 SQL 从生成到结果返回的全流程:
在这里插入图片描述
简单说:想监控 SQL 执行时间,拦Executor;想改 SQL(比如加租户条件),拦StatementHandler;想脱敏返回结果,拦ResultSetHandler。

拦截器必须实现Interceptor接口,核心就 3 个方法:

public interface Interceptor {// 核心:拦截逻辑写这里Object intercept(Invocation invocation) throws Throwable;// 生成代理对象(直接用Plugin.wrap()就行)Object plugin(Object target);// 读取配置参数(比如从配置文件拿慢查询阈值)void setProperties(Properties properties);
}

还要用@Intercepts和@Signature注解告诉 MyBatis 要拦谁、拦哪个方法:

// 示例:拦截StatementHandler的prepare方法(SQL预编译阶段)
@Intercepts({@Signature(type = StatementHandler.class, // 拦截哪个组件method = "prepare", // 拦截组件的哪个方法args = {Connection.class, Integer.class} // 方法参数类型(必须严格匹配))
})
public class MySqlInterceptor implements Interceptor {// 实现接口方法...
}

踩坑提醒:args参数必须和方法实际参数类型完全一致!比如prepare方法有两个重载,记错参数类型就会拦截失败。

二、实战一:慢查询监控拦截器(一行代码定位超时 SQL)

需求:自动记录所有 SQL 的执行时间,超过 500ms 就报警,包含完整 SQL 和参数。实现步骤:

  1. 写拦截器逻辑(拦 Executor 的 query 和 update 方法)
@Slf4j
@Intercepts({// 拦截查询方法@Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),// 拦截增删改方法@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class})
})
public class SlowSqlInterceptor implements Interceptor {// 慢查询阈值(默认500ms,可配置)private long slowThreshold=500;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 记录开始时间long startTime = System.currentTimeMillis();try {// 2. 执行原SQL(继续流程)return invocation.proceed();} finally {// 3. 计算耗时long costTime = System.currentTimeMillis() - startTime;// 4. 获取SQL和参数MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];String sql = mappedStatement.getBoundSql(parameter).getSql(); // 带?的SQL// 5. 慢查询报警if (costTime > slowThreshold) {log.warn("[慢查询警告] 耗时: {}ms, SQL: {}, 参数: {}", costTime, sql, parameter);} else {log.info("[SQL监控] 耗时: {}ms, SQL: {}", costTime, sql);}}}@Overridepublic Object plugin(Object target) {// 生成代理对象(MyBatis工具方法,不用自己写)return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// 从配置文件读阈值(比如application.yml里配)String threshold = properties.getProperty("slowThreshold");if (threshold != null) {this.slowThreshold = Long.parseLong(threshold);}}
}
  1. 注册拦截器到 SpringBoot
@Configuration
@MapperScan("com.example.mapper")// 扫描Mapper接口
public class MyBatisConfig {// 注册慢查询拦截器@Beanpublic SlowSqlInterceptor slowSqlInterceptor() {SlowSqlInterceptor interceptor = new SlowSqlInterceptor();// 配置阈值(也可以在application.yml里配)Properties props = new Properties();props.setProperty("slowThreshold", "500"); // 500msinterceptor.setProperties(props);return interceptor;}// 把拦截器加到SqlSessionFactory@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource, SlowSqlInterceptor slowSqlInterceptor) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// 设置Mapper.xml路径(如果需要)sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));// 关键:添加拦截器sessionFactory.setPlugins(slowSqlInterceptor);return sessionFactory.getObject();}
}
  1. 测试效果

调用任意查询接口,控制台会自动打印:

[SQL监控] 耗时: 32ms, SQL: SELECT id,username,phone FROM user WHERE id = ? 

如果 SQL 执行超过 500ms(比如查大数据量表):

[慢查询警告] 耗时: 1200ms, SQL: SELECT * FROM order WHERE user_id = ?, 参数: 10086 

关键优势:不用改任何 Service 或 Mapper 代码,所有 SQL 自动被监控。

三、实战二:数据脱敏拦截器(手机号、身份证自动打码)

需求:查询用户信息时,自动把手机号(13812345678→1385678)、身份证号(110…1234→************34)脱敏。

实现步骤:

  1. 自定义脱敏注解(标记需要脱敏的字段)
// 作用在字段上的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {SensitiveType type(); // 脱敏类型(手机号/身份证)
}// 脱敏类型枚举
public enum SensitiveType {PHONE, ID_CARD
}
  1. 实体类加注解(告诉哪些字段要脱敏)
@Data
public class User {private Long id;private String username;@Sensitive(type = SensitiveType.PHONE) // 手机号脱敏private String phone;@Sensitive(type = SensitiveType.ID_CARD) // 身份证脱敏private String idCard;
}
  1. 脱敏工具类(实现具体打码逻辑)
public class SensitiveUtils {// 手机号脱敏:保留前3后4public static String maskPhone(String phone) {if (phone == null || phone.length() != 11) {return phone; // 非手机号不处理}return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");}// 身份证脱敏:保留最后2位public static String maskIdCard(String idCard) {if (idCard == null || idCard.length() < 18) {return idCard; // 非身份证不处理}return idCard.replaceAll("\\d{16}(\\d{2})", "****************$1");}
}
  1. 写结果集拦截器(拦 ResultSetHandler 处理返回结果)
@Slf4j
@Intercepts({@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class} // 拦截结果处理方法)
})
public class SensitiveInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 1. 先执行原方法,拿到查询结果Object result = invocation.proceed();// 2. 如果是List,遍历处理每个元素if (result instanceof List<?>) {List<?> resultList = (List<?>) result;for (Object obj : resultList) {desensitize(obj); // 脱敏处理}}return result;}// 反射处理对象中的敏感字段private void desensitize(Object obj) throws IllegalAccessException {if (obj == null) return;Class<?> clazz = obj.getClass();// 遍历所有字段for (Field field : clazz.getDeclaredFields()) {// 3. 检查字段是否有@Sensitive注解if (field.isAnnotationPresent(Sensitive.class)) {Sensitive annotation = field.getAnnotation(Sensitive.class);field.setAccessible(true); // 允许访问私有字段Object value = field.get(obj); // 获取字段值// 4. 根据类型脱敏if (value instanceof String) {String strValue = (String) value;switch (annotation.type()) {case PHONE:field.set(obj, SensitiveUtils.maskPhone(strValue));break;case ID_CARD:field.set(obj, SensitiveUtils.maskIdCard(strValue));break;}}}}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
  1. 注册多个拦截器(注意顺序!)
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {// 慢查询拦截器(先注册)@Beanpublic SlowSqlInterceptor slowSqlInterceptor() { ... }// 脱敏拦截器(后注册)@Beanpublic SensitiveInterceptor sensitiveInterceptor() {return new SensitiveInterceptor();}// 关键:多个拦截器的顺序就是执行顺序@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource,SlowSqlInterceptor slowSqlInterceptor,SensitiveInterceptor sensitiveInterceptor) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// 按执行顺序添加:先监控SQL,再处理结果sessionFactory.setPlugins(slowSqlInterceptor, sensitiveInterceptor);return sessionFactory.getObject();}
}
  1. 测试效果
    查询用户后返回的对象自动脱敏:
User user = userService.getById(1L);
System.out.println(user); 
// 输出:User(id=1, username=张三, phone=138****5678, idCard=****************34)

三、避坑指南:这 3 个错误 90% 的人都会犯

  1. 拦截器顺序搞反

多个拦截器时,注册顺序就是执行顺序。比如先注册脱敏拦截器,再注册慢查询拦截器,会导致慢查询日志里的参数已经被脱敏,排查问题时看不到原始值。

正确顺序:按 “SQL 执行前→执行中→执行后” 排序,比如:权限拦截器(改 SQL)→慢查询拦截器(监控)→脱敏拦截器(处理结果)。

  1. @Signature 的 args 参数写错

拦截不到方法大概率是因为args参数类型和目标方法不一致。比如StatementHandler.prepare方法的参数是(Connection, Integer),写成(Connection, int)就会报错:

// 错误示例:Integer写成int
@Signature(args = {Connection.class, int.class})
// 正确写法:
@Signature(args = {Connection.class, Integer.class})

解决:用 IDE 看目标方法的参数类型,严格复制过来。

  1. 拦截器里做复杂操作导致性能暴跌

反射遍历字段(比如脱敏拦截器)、频繁创建对象会拖慢 SQL 执行。

优化:

  • 缓存反射获取的 Class 和 Field 信息(用ConcurrentHashMap存)

  • 非必要不拦截(比如只拦查询,不拦更新)

  • 复杂逻辑异步处理(比如慢查询日志用异步线程打印)

http://www.xdnf.cn/news/1485289.html

相关文章:

  • KMeans聚类
  • 项目介绍:图像分类项目的最小可用骨架--代码细节讲解
  • 关于学习的一些感悟
  • HTTP原理
  • Archon01-项目部署
  • SQLAlchemy ORM-表与表之间的关系
  • Python快速入门专业版(九):字符串进阶:常用方法(查找、替换、分割、大小写转换)
  • Text2Sql.Net架构深度解析:从自然语言到SQL的智能转换之道
  • 2025算法八股——大模型开发——指令微调
  • 跳转原生系统设置插件 支持安卓/iOS/鸿蒙UTS组件
  • 安卓学习 之 ProgressBar(进度条)控件
  • Android 热点开发的相关api总结
  • Python-LLMChat
  • 《数据结构全解析:栈(数组实现)》
  • Dockerfile解析器指令(Parser Directive)指定语法版本,如:# syntax=docker/dockerfile:1
  • 【Java实战㉛】解锁Spring框架实战:深入IOC容器的奇妙之旅
  • 从0开始制做一个Agent
  • CMake构建和调试简单程序(windows)
  • YOLO11实战 第009期-基于yolo11的咖啡叶病害目标检测实战文档(yolo格式数据免费获取)
  • C++进阶——多态
  • 简述ajax、node.js、webpack、git
  • ncnn-Android-mediapipe_hand 踩坑部署实录
  • 【数据结构】经典 Leetcode 题
  • Java安全体系深度研究:技术演进与攻防实践
  • 嵌入式Secure Boot安全启动详解
  • JSP到Tomcat特详细教程
  • C#中的托管资源与非托管资源介绍
  • Docker启动失败 Failed to start Docker Application Container Engine.
  • ZYNQ SDK软件在线调试
  • Flutter SDK 安装与国内镜像配置全流程(Windows / macOS / Linux)