设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间22 现在时间22 缓存数据 关关难过关关过,夜夜难熬夜夜熬。万般皆苦,悲欢自渡,他人难悟。晚安!

关关难过关关过,夜夜难熬夜夜熬。万般皆苦,悲欢自渡,他人难悟。晚安!

查看: 779|回复: 0

SpringBoot集成MyBatis实现SQL拦截器的实战指南

[复制链接]

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
46
主题
32
精华
0
金钱
120
积分
78
注册时间
2023-10-4
最后登录
2025-8-29

发表于 2025-8-28 02:59:08 | 显示全部楼层 |阅读模式

一、为什么需要SQL拦截器?

先看几个真实场景:

  • 慢查询监控:生产环境突然出现接口超时,需要快速定位执行时间过长的SQL
  • 数据脱敏:用户表查询结果中的手机号、身份证号需要自动替换为****
  • 权限控制:多租户系统中,自动给SQL添加tenant_id = ?条件,防止数据越权访问
  • SQL审计:记录所有执行的SQL语句、执行人、执行时间,满足合规要求

如果没有拦截器,这些需求可能需要修改每一个Mapper接口或Service方法,工作量巨大。

而MyBatis的SQL拦截器能在SQL执行的各个阶段进行拦截处理,实现"无侵入式"增强。

二、MyBatis拦截器基础

2.1 核心接口:Interceptor

MyBatis的拦截器机制基于JDK动态代理,所有自定义拦截器都要实现Interceptor接口:

  1. public interface Interceptor {
  2. // 拦截逻辑的核心方法
  3. Object intercept(Invocation invocation) throws Throwable;
  4. // 生成代理对象(通常直接用Plugin.wrap())
  5. Object plugin(Object target);
  6. // 读取配置参数(如从mybatis-config.xml中获取)
  7. void setProperties(Properties properties);
  8. }
复制代码

2.2 拦截目标与签名配置

MyBatis允许拦截4个核心组件的方法,通过@Intercepts和@Signature注解指定拦截目标:

1.jpeg

举个栗子:拦截StatementHandler的prepare方法(SQL预编译阶段):

  1. @Intercepts({
  2. @Signature(
  3. type = StatementHandler.class, // 拦截哪个接口
  4. method = "prepare", // 拦截接口的哪个方法
  5. args = {Connection.class, Integer.class} // 方法参数类型(用于确定重载方法)
  6. )
  7. })
  8. public class MySqlInterceptor implements Interceptor {
  9. // 实现接口方法...
  10. }
复制代码

注意:args参数必须严格匹配方法的参数类型,否则拦截不到!比如prepare方法有两个重载,这里指定(Connection, Integer)类型的参数。

三、实战一:慢查询监控拦截器

3.1 需求说明

监控所有SQL执行时间,超过阈值(如500ms)则打印警告日志,包含:

  • SQL执行时间
  • 完整SQL语句(带参数占位符)
  • 参数值(防止SQL注入排查)

3.2 完整实现代码

(1)拦截器类

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.apache.ibatis.executor.statement.StatementHandler;
  3. import org.apache.ibatis.plugin.*;
  4. import org.apache.ibatis.session.ResultHandler;
  5. import java.sql.Connection;
  6. import java.sql.Statement;
  7. import java.util.Properties;
  8. @Slf4j
  9. @Intercepts({
  10. // 拦截查询方法
  11. @Signature(
  12. type = StatementHandler.class,
  13. method = "query",
  14. args = {Statement.class, ResultHandler.class}
  15. ),
  16. // 拦截更新方法(insert/update/delete)
  17. @Signature(
  18. type = StatementHandler.class,
  19. method = "update",
  20. args = {Statement.class}
  21. )
  22. })
  23. public class SlowSqlInterceptor implements Interceptor {
  24. // 慢查询阈值(毫秒),可通过配置文件注入
  25. private long slowThreshold = 500;
  26. @Override
  27. public Object intercept(Invocation invocation) throws Throwable {
  28. // 1. 记录开始时间
  29. long startTime = System.currentTimeMillis();
  30. try {
  31. // 2. 执行原方法(继续SQL执行流程)
  32. return invocation.proceed();
  33. } finally {
  34. // 3. 计算执行耗时(无论成功失败都记录)
  35. long costTime = System.currentTimeMillis() - startTime;
  36. // 4. 获取SQL语句和参数
  37. StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
  38. String sql = statementHandler.getBoundSql().getSql(); // 获取SQL语句(带?占位符)
  39. Object parameterObject = statementHandler.getBoundSql().getParameterObject(); // 获取参数
  40. // 5. 判断是否慢查询
  41. if (costTime > slowThreshold) {
  42. log.warn("[慢查询警告] 执行时间: {}ms, SQL: {}, 参数: {}",
  43. costTime, sql, parameterObject);
  44. } else {
  45. log.info("[SQL监控] 执行时间: {}ms, SQL: {}", costTime, sql);
  46. }
  47. }
  48. }
  49. @Override
  50. public Object plugin(Object target) {
  51. // 生成代理对象(MyBatis提供的工具方法,避免自己写代理逻辑)
  52. return Plugin.wrap(target, this);
  53. }
  54. @Override
  55. public void setProperties(Properties properties) {
  56. // 从配置文件读取阈值(如application.yml中配置)
  57. String threshold = properties.getProperty("slowThreshold");
  58. if (threshold != null) {
  59. slowThreshold = Long.parseLong(threshold);
  60. }
  61. }
  62. }
复制代码

(2)SpringBoot注册拦截器

  1. package com.example.config;
  2. import com.example.interceptor.SensitiveInterceptor;
  3. import com.example.interceptor.SlowSqlInterceptor;
  4. import org.apache.ibatis.session.SqlSessionFactory;
  5. import org.mybatis.spring.SqlSessionFactoryBean;
  6. import org.mybatis.spring.annotation.MapperScan;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  10. import javax.sql.DataSource;
  11. import java.util.Properties;
  12. @Configuration
  13. @MapperScan("com.example.mapper") // Mapper接口所在包
  14. public class MyBatisConfig {
  15. // 注册慢查询拦截器
  16. @Bean
  17. public SlowSqlInterceptor slowSqlInterceptor() {
  18. SlowSqlInterceptor interceptor = new SlowSqlInterceptor();
  19. // 设置属性(也可通过application.yml配置)
  20. Properties properties = new Properties();
  21. properties.setProperty("slowThreshold", "500"); // 慢查询阈值500ms
  22. interceptor.setProperties(properties);
  23. return interceptor;
  24. }
  25. @Bean
  26. public SensitiveInterceptor sensitiveInterceptor() {
  27. return new SensitiveInterceptor();
  28. }
  29. // 将拦截器添加到SqlSessionFactory
  30. @Bean
  31. public SqlSessionFactory sqlSessionFactory(DataSource dataSource, SlowSqlInterceptor slowSqlInterceptor) throws Exception {
  32. SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  33. sessionFactory.setDataSource(dataSource);
  34. // 设置Mapper.xml路径(如果需要)
  35. /*sessionFactory.setMapperLocations(
  36. new PathMatchingResourcePatternResolver()
  37. .getResources("classpath:mapper/*.xml")
  38. );*/
  39. // 添加拦截器
  40. sessionFactory.setPlugins(slowSqlInterceptor);
  41. return sessionFactory.getObject();
  42. }
  43. }
  44. (3)测试效果
  45. 写个简单的查询接口:
  46. @Service
  47. public class UserService {
  48. @Autowired
  49. private UserMapper userMapper;
  50. public User getUserById(Long id) {
  51. return userMapper.selectById(id);
  52. }
  53. }
复制代码

执行后控制台输出:

  1. [SQL监控] 执行时间: 30ms, SQL: SELECT id,username,phone FROM user WHERE id = ?如果SQL执行时间超过500ms(比如查询大数据量表):[慢查询警告] 执行时间: 1430ms, SQL: SELECT * FROM user WHERE id = ?, 参数: {id=1, param1=1}踩坑提示:如果拦截不到SQL,检查@Signature注解的args参数是否与方法参数类型完全匹配!
复制代码

四、实战二:数据脱敏拦截器(敏感信息保护)

4.1 需求说明

查询用户信息时,自动将敏感字段脱敏:

  • 手机号:13812345678 → 138****5678
  • 身份证号:110101199001011234 → ****************34

4.2 完整实现代码

(1)自定义脱敏注解

  1. import java.lang.annotation.*;
  2. // 作用在字段上
  3. @Target(ElementType.FIELD)
  4. // 运行时生效
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface Sensitive {
  7. // 脱敏类型(手机号、身份证号等)
  8. SensitiveType type();
  9. }
  10. // 脱敏类型枚举
  11. public enum SensitiveType {
  12. PHONE, // 手机号
  13. ID_CARD // 身份证号
  14. }
复制代码

(2)实体类添加注解

  1. import lombok.Data;
  2. @Data
  3. public class User {
  4. private Long id;
  5. private String username;
  6. @Sensitive(type = SensitiveType.PHONE) // 手机号脱敏
  7. private String phone;
  8. @Sensitive(type = SensitiveType.ID_CARD) // 身份证号脱敏
  9. private String idCard;
  10. }
复制代码

(3)脱敏工具类

  1. public class SensitiveUtils {
  2. // 手机号脱敏:保留前3位和后4位
  3. public static String maskPhone(String phone) {
  4. if (phone == null || phone.length() != 11) {
  5. return phone; // 非手机号格式不处理
  6. }
  7. return phone.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
  8. }
  9. // 身份证号脱敏:保留最后2位
  10. public static String maskIdCard(String idCard) {
  11. if (idCard == null || idCard.length() < 18) {
  12. return idCard; // 非身份证格式不处理
  13. }
  14. return idCard.replaceAll("\d{16}(\d{2})", "****************$1");
  15. }
  16. }
复制代码

(4)结果集拦截器

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.apache.ibatis.executor.resultset.ResultSetHandler;
  3. import org.apache.ibatis.plugin.*;
  4. import java.lang.reflect.Field;
  5. import java.sql.Statement;
  6. import java.util.List;
  7. import java.util.Properties;
  8. @Slf4j
  9. @Intercepts({
  10. @Signature(
  11. type = ResultSetHandler.class,
  12. method = "handleResultSets",
  13. args = {Statement.class}
  14. )
  15. })
  16. public class SensitiveInterceptor implements Interceptor {
  17. @Override
  18. public Object intercept(Invocation invocation) throws Throwable {
  19. // 1. 执行原方法,获取查询结果
  20. Object result = invocation.proceed();
  21. // 2. 如果结果是List,遍历处理每个元素
  22. if (result instanceof List<?>) {
  23. List<?> resultList = (List<?>) result;
  24. for (Object obj : resultList) {
  25. // 3. 对有@Sensitive注解的字段进行脱敏
  26. desensitize(obj);
  27. }
  28. }
  29. return result;
  30. }
  31. // 反射处理对象中的敏感字段
  32. private void desensitize(Object obj) throws IllegalAccessException {
  33. if (obj == null) {
  34. return;
  35. }
  36. Class<?> clazz = obj.getClass();
  37. Field[] fields = clazz.getDeclaredFields(); // 获取所有字段(包括私有)
  38. for (Field field : fields) {
  39. // 4. 检查字段是否有@Sensitive注解
  40. if (field.isAnnotationPresent(Sensitive.class)) {
  41. Sensitive annotation = field.getAnnotation(Sensitive.class);
  42. field.setAccessible(true); // 开启私有字段访问权限
  43. Object value = field.get(obj); // 获取字段值
  44. if (value instanceof String) {
  45. String strValue = (String) value;
  46. // 5. 根据脱敏类型处理
  47. switch (annotation.type()) {
  48. case PHONE:
  49. field.set(obj, SensitiveUtils.maskPhone(strValue));
  50. break;
  51. case ID_CARD:
  52. field.set(obj, SensitiveUtils.maskIdCard(strValue));
  53. break;
  54. default:
  55. break;
  56. }
  57. }
  58. }
  59. }
  60. }
  61. @Override
  62. public Object plugin(Object target) {
  63. return Plugin.wrap(target, this);
  64. }
  65. @Override
  66. public void setProperties(Properties properties) {
  67. // 可配置更多脱敏规则,此处省略
  68. }
  69. }
复制代码

(5)注册多个拦截器

修改MyBatisConfig,添加脱敏拦截器:

  1. @Configuration
  2. @MapperScan("com.example.mapper")
  3. public class MyBatisConfig {
  4. // ... 慢查询拦截器配置 ...
  5. @Bean
  6. public SensitiveInterceptor sensitiveInterceptor() {
  7. return new SensitiveInterceptor();
  8. }
  9. @Bean
  10. public SqlSessionFactory sqlSessionFactory(DataSource dataSource,
  11. SlowSqlInterceptor slowSqlInterceptor,
  12. SensitiveInterceptor sensitiveInterceptor) throws Exception {
  13. SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  14. sessionFactory.setDataSource(dataSource);
  15. sessionFactory.setMapperLocations(
  16. new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
  17. );
  18. // 注册多个拦截器(注意顺序!先执行的拦截器先注册)
  19. sessionFactory.setPlugins(slowSqlInterceptor, sensitiveInterceptor);
  20. return sessionFactory.getObject();
  21. }
  22. }
复制代码

(6)测试效果

查询用户信息:

  1. User user = userService.getUserById(1L);
  2. System.out.println(user);
  3. // 输出:User(id=1, username=张三, phone=138****5678, idCard=****************34)
复制代码

五、实战踩坑指南

5.1 拦截器顺序问题

坑:多个拦截器时,注册顺序就是执行顺序。比如先注册慢查询拦截器,再注册脱敏拦截器:

SQL执行 → 慢查询拦截器(记录时间) → 脱敏拦截器(处理结果)
如果顺序反了,脱敏拦截器会先处理结果,慢查询拦截器记录的SQL就看不到原始参数了。

解决:按"执行SQL前→执行SQL后→处理结果"的顺序注册。

5.2 拦截器签名配置错误

坑:@Signature的args参数类型写错,导致拦截不到方法。比如StatementHandler.prepare方法有两个重载:

  1. // 正确的参数类型
  2. prepare(Connection connection, Integer transactionTimeout)
  3. // 错误示例:写成了(int)
  4. @Signature(args = {Connection.class, int.class}) // 出现下面的异常!
复制代码

java.lang.NoSuchMethodException: org.apache.ibatis.executor.statement.StatementHandler.prepare(java.sql.Connection,int)

解决:通过IDE查看方法参数类型,确保完全一致。

5.3 性能问题

坑:在拦截器中做复杂操作(如反射遍历所有字段)会影响性能。

解决:

  • 反射操作缓存Class信息
  • 非必要不拦截(如只拦截查询方法)
  • 敏感字段脱敏可考虑在DTO层处理

到此这篇关于SpringBoot集成MyBatis实现SQL拦截器的实战指南的文章就介绍到这了,更多相关SpringBoot MyBatis拦截SQL内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表