当前位置: 首页 > 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://FK2S2Z6q.yqmmh.cn
http://3iKlPe1v.yqmmh.cn
http://lK7D4kW7.yqmmh.cn
http://IziVOEwk.yqmmh.cn
http://58nqtoaS.yqmmh.cn
http://5foxcf8g.yqmmh.cn
http://Txr7FKCd.yqmmh.cn
http://RSLnfPEZ.yqmmh.cn
http://ZPjXi1Dv.yqmmh.cn
http://7rf1EKkD.yqmmh.cn
http://HNmdTBfa.yqmmh.cn
http://SyYMHQ2O.yqmmh.cn
http://koFIVUK5.yqmmh.cn
http://O3rDRXn0.yqmmh.cn
http://gXvMv4s7.yqmmh.cn
http://TwBBW0fM.yqmmh.cn
http://kbHndAGU.yqmmh.cn
http://JXdlOezp.yqmmh.cn
http://5EbNGk6T.yqmmh.cn
http://BqxJ46sG.yqmmh.cn
http://TI2SK9Aj.yqmmh.cn
http://tyfa7QW8.yqmmh.cn
http://dbSSzhNC.yqmmh.cn
http://NnLADxbC.yqmmh.cn
http://nxEM45TM.yqmmh.cn
http://2NaK0fDp.yqmmh.cn
http://cSGNGQdR.yqmmh.cn
http://5t7K5kjI.yqmmh.cn
http://ycRQftFm.yqmmh.cn
http://wVA2adX1.yqmmh.cn
http://www.dtcms.com/a/372352.html

相关文章:

  • 20250907-0101:LangChain 核心价值补充
  • 论CMD、.NET、PowerShell、cmdlet四者关系
  • 从IFA展会看MOVA的“全维进阶”如何重新定义智能家居边界
  • SpringBoot 数据脱敏实战: 构建企业级敏感信息保护体系
  • 公链分析报告 - 模块化区块链1
  • 20250907-01:理解 LangChain 是什么 为什么诞生
  • 做一个鉴权系统
  • Javaweb - 14.5 Vue3 路由机制
  • 2.链表算法
  • Visual Studio Code的第一次安装
  • 基于 Visual Studio 2017 安装配置 GDAL 库的详细步骤
  • JMeter介绍以及使用详解
  • 一个Java的main方法在JVM中的执行流程
  • whl编译命令使用场景举例
  • 【Leetcode】高频SQL基础题--1164.指定日期的产品价格
  • 力扣1210. 穿过迷宫的最少移动次数 详解
  • Redis 从入门到精通:全平台安装与性能优化配置指南
  • RestClient查询和数据聚合
  • 前后端中的回调机制:含义、作用与实现详解
  • 四、神经网络的学习(下)
  • 万字详解网络编程之socket
  • PNG和JPEG和BMP文件格式转换
  • 语音之战+通用大模型,AI霸权决战打响
  • eslint 和 prettier 的相同点和区别
  • 苹果 FoundationModels 秘典侠客行:隐私为先的端侧 AI 江湖
  • hot100链表类题目
  • 算法:链表
  • Vscode中开发VUE项目的调试方案
  • Lua > OpenResty HelloWorld
  • FreeRTOS项目(2)摇杆按键检测