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

本文主要是介绍SpringBoot集成MyBatis实现SQL拦截器的实战指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot集成MyBatis实现SQL拦截器的实战指南》这篇文章主要为大家详细介绍了SpringBoot集成MyBatis实现SQL拦截器的相关知识,文中的示例代码讲解详细,有需要的小伙伴...

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

先看几个真实场景:

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

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

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

二、MyBatis拦截器基础

2.1 核心接口:Interceptor

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

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

2.2 拦截目标与签名配置

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

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

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

@Intercepts({
    @Signature(
        type = StatementHandler.class,  // 拦截哪个接口
        method = "prepare",             // 拦截接口的哪个方法
        args = {Connection.class, Integer.class}  // 方法参数类型(用于确定重载方法)
    )
})
public class mysqlInterceptor implemphpents Interceptor {
    // 实现接口方法...
}

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

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

3.1 需求说明

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

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

3.2 完整实现代码

(1)拦截器类

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import Java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;

@Slf4j
@Intercepts({
    // 拦截查询方法
    @Signature(
        type = StatementHandler.class,
        method = "query",
        args = {Statement.class, ResultHandler.class}
    ),
    // 拦截更新方法(insert/update/delete)
    @Signature(
        type = StatementHandler.class,
        method = "update",
        args = {Statement.class}
    )
})
public class SlowSqlInterceptor implements Interceptor {

    // 慢查询阈值(毫秒),可通过配置文件注入
    private long slowThreshold = 500;

    @Override
    public 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语句和参数
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            String sql = statementHandler.getBoundSql().getSql();  // 获取SQL语句(带?占位符)
            Object parameterObject = statementHandler.getBoundSql().getParameterObject();  // 获取参数
            
            // 5. 判断是否慢查询
            if (costTime > slowThreshold) {
                log.warn("[慢查询警告] 执行时间: {}ms, SQL: {}, 参数: {}", 
                         costTime, sql, parameterObject);
            } else {
                log.info("[SQL监控] 执行时间: {}ms, SQL: {}", costTime, sql);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 生成代理对象(MyBatis提供的工具方法,避免自己写代理逻辑)
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 从配置文件读取阈值(如application.yml中配置)
        String threshold = properties.getProperty("slowThreshold");
        if (threshold != null) {
            slowThreshold = Long.parseLong(threshold);
        }
    }
}

(2)SpringBoot注册拦截器

package com.example.config;

import com.example.interceptor.SensitiveInterceptor;
import com.example.interceptor.SlowSqlInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@MapperScan("com.example.mapper")  // Mapper接口所在包
public class MyBatisConfig {

   hdSufoGkq // 注册慢查询拦截器
    @Bean
    public SlowSqlInterceptor slowSqlInterceptor() {
        SlowSqlInterceptor interceptor = new SlowSqlInterceptor();
        
        // 设置属性(也可通过application.yml配置)
        Properties properties = new Properties();
        properties.setProperty("slowThreshold", "500");  // 慢查询阈值500ms
        interceptor.setProperties(properties);
        
        return interceptor;
    }

    @Bean
    public SensitiveInterceptor sensitiveInterceptor() {
        return new SensitiveInterceptor();
    }

    // 将拦截器添加到SqlSessionFactory
    @Bean
    public 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")
        );*/
        
        android// 添加拦截器
        sessionFactory.setPlugins(slowSqlInterceptor);

        return sessionFactory.getObject();
    }
}
(3)测试效果
写个简单的查询接口:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

执行后控制台输出:

[SQL监控hdSufoGkq] 执行时间: 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)自定义脱敏注解

import japhpva.lang.annotation.*;

// 作用在字段上
@Target(ElementType.FIELD)
// 运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
    // 脱敏类型(手机号、身份证号等)
    SensitiveType type();
}

// 脱敏类型枚举
public enum SensitiveType {
    PHONE,    // 手机号
    ID_CARD   // 身份证号
}

(2)实体类添加注解

import lombok.Data;

@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;
}

(3)脱敏工具类

public class SensitiveUtils {
    // 手机号脱敏:保留前3位和后4位
    public 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");
    }
}

(4)结果集拦截器

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

@Slf4j
@Intercepts({
    @Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
    )
})
public class SensitiveInterceptor implements Interceptor {

    @Override
    public 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) {
                // 3. 对有@Sensitive注解的字段进行脱敏
                desensitize(obj);
            }
        }
        return result;
    }

    // 反射处理对象中的敏感字段
    private void desensitize(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return;
        }
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();  // 获取所有字段(包括私有)
        
        for (Field field : fields) {
            // 4. 检查字段是否有@Sensitive注解
            if (field.isAnnotationPresent(Sensitive.class)) {
                Sensitive annotation = field.getAnnotation(Sensitive.class);
                field.setAccessible(true);  // 开启私有字段访问权限
                Object value = field.get(obj);  // 获取字段值
                
                if (value instanceof String) {
                    String strValue = (String) value;
                    // 5. 根据脱敏类型处理
                    switch (annotation.type()) {
                        case PHONE:
                            field.set(obj, SensitiveUtils.maskPhone(strValue));
                            break;
                        case ID_CARD:
                            field.set(obj, SensitiveUtils.maskIdCard(strValue));
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置更多脱敏规则,此处省略
    }
}

(5)注册多个拦截器

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

@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
    // ... 慢查询拦截器配置 ...

    @Bean
    public SensitiveInterceptor sensitiveInterceptor() {
        return new SensitiveInterceptor();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, 
                                             SlowSqlInterceptor slowSqlInterceptor,
                                             SensitiveInterceptor sensitiveInterceptor) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
        );
        
        // 注册多个拦截器(注意顺序!先执行的拦截器先注册)
        sessionFactory.setPlugins(slowSqlInterceptor, sensitiveInterceptor);
        
        return sessionFactory.getObject();
    }
}

(6)测试效果

查询用户信息:

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

五、实战踩坑指南

5.1 拦截器顺序问题

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

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

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

5.2 拦截器签名配置错误

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

// 正确的参数类型
prepare(Connection connection, Integer transactionTimeout)
// 错误示例:写成了(int)
@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内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于SpringBoot集成MyBatis实现SQL拦截器的实战指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155438

相关文章

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st

Spring Boot3.0新特性全面解析与应用实战

《SpringBoot3.0新特性全面解析与应用实战》SpringBoot3.0作为Spring生态系统的一个重要里程碑,带来了众多令人兴奋的新特性和改进,本文将深入解析SpringBoot3.0的... 目录核心变化概览Java版本要求提升迁移至Jakarta EE重要新特性详解1. Native Ima

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三

我们来说说Java LockSupport 的 park 和 unpark

《我们来说说JavaLockSupport的park和unpark》LockSupport是JDK底层线程阻塞工具,通过park/unpark实现线程阻塞与唤醒,避免死锁,与Object的w... 目录一、LockSupport1.1、LockSupport函数列表1.2、基本使用先 park 再 unpa

SpringBoot集成EasyPoi实现Excel模板导出成PDF文件

《SpringBoot集成EasyPoi实现Excel模板导出成PDF文件》在日常工作中,我们经常需要将数据导出成Excel表格或PDF文件,本文将介绍如何在SpringBoot项目中集成EasyPo... 目录前言摘要简介源代码解析应用场景案例优缺点分析类代码方法介绍测试用例小结前言在日常工作中,我们经

基于Python实现简易视频剪辑工具

《基于Python实现简易视频剪辑工具》这篇文章主要为大家详细介绍了如何用Python打造一个功能完备的简易视频剪辑工具,包括视频文件导入与格式转换,基础剪辑操作,音频处理等功能,感兴趣的小伙伴可以了... 目录一、技术选型与环境搭建二、核心功能模块实现1. 视频基础操作2. 音频处理3. 特效与转场三、高

Python实现中文文本处理与分析程序的示例详解

《Python实现中文文本处理与分析程序的示例详解》在当今信息爆炸的时代,文本数据的处理与分析成为了数据科学领域的重要课题,本文将使用Python开发一款基于Python的中文文本处理与分析程序,希望... 目录一、程序概述二、主要功能解析2.1 文件操作2.2 基础分析2.3 高级分析2.4 可视化2.5

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张