• MyBatis 的 SQL 拦截器:原理、实现与实践

MyBatis 的 SQL 拦截器:原理、实现与实践

2025-08-16 12:33:00 栏目:宝塔面板 93 阅读

前言

在MyBatis框架的使用过程中,我们常常需要对SQL执行过程进行干预 —— 比如打印执行日志、统计执行时间、动态修改SQL语句,甚至实现数据权限控制。而MyBatis提供的SQL拦截器(Interceptor)机制,正是实现这些需求的核心工具。

核心原理

MyBatis的SQL拦截器本质上是基于JDK动态代理实现的插件机制,它允许开发者在 SQL 执行的关键节点插入自定义逻辑。要理解其原理,需先明确两个核心概念:拦截目标与代理机制。

核心接口
  • Executor:MyBatis的核心执行器,负责SQL的整体执行(如select、update、commit等),是最常用的拦截目标。
  • StatementHandler:处理SQL语句的准备(如创建 Statement)、参数设置、结果集映射等,可用于修改SQL语句或参数。
  • ParameterHandler:处理SQL参数的设置(如为PreparedStatement设置参数),适合拦截参数并进行加工。
  • ResultSetHandler:处理查询结果集的映射(如将结果映射为Java对象),可用于修改返回结果。
代理机制

MyBatis的拦截器通过动态代理 + 责任链模式工作:当定义一个拦截器后,MyBatis会为被拦截的接口生成代理对象,将拦截逻辑嵌入代理对象中;若存在多个拦截器,则会形成代理链(外层代理调用内层代理,最终调用原始对象)。 具体流程如下:

  • 拦截器通过@Intercepts注解声明拦截目标(接口、方法、参数);
  • MyBatise 启动时扫描拦截器,为目标接口创建代理对象;
  • 当调用目标接口的方法时,代理对象先执行拦截器的intercept方法(自定义逻辑),再调用原始方法;
  • 若有多个拦截器,代理对象会按顺序执行所有拦截逻辑后,再执行原始方法。

实现步骤

实现一个MyBatis SQL拦截器需遵循固定流程:定义拦截器类、声明拦截目标、实现拦截逻辑,最后配置生效。下面以SQL 执行时间统计为例,详解具体实现。

定义拦截器类:实现 Interceptor 接口

该接口包含3个核心方法:

  • intercept(Invocation invocation):核心方法,拦截逻辑的实现(如统计时间、修改参数)。
  • plugin(Object target):决定是否为目标对象生成代理(通常通过Plugin.wrap(target, this)实现)。
  • setProperties(Properties properties):接收配置文件中传入的参数(如拦截器开关、日志级别)。
// 声明拦截目标:拦截Executor的query和update方法
@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 SqlExecuteTimeInterceptor implements Interceptor {

    // 拦截逻辑:统计SQL执行时间
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 记录开始时间
        long startTime = System.currentTimeMillis();
        try {
            // 2. 执行原始方法(如query/update)
            return invocation.proceed();
        } finally {
            // 3. 计算执行时间并打印
            long endTime = System.currentTimeMillis();
            long cost = endTime - startTime;
            // 获取SQL语句(从MappedStatement中提取)
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            String sqlId = mappedStatement.getId(); // Mapper接口方法全路径
            System.out.printf("SQL执行:%s,耗时:%d ms%n", sqlId, cost);
        }
    }

    // 生成代理对象(固定写法)
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    // 接收配置参数(如无需参数可空实现)
    @Override
    public void setProperties(Properties properties) {
        // 例如:从配置中获取阈值,超过阈值打印警告
        String threshold = properties.getProperty("slowSqlThreshold");
        if (threshold != null) {
            // 处理参数...
        }
    }
}

声明拦截目标:@Intercepts 与 @Signature

拦截器必须通过@Intercepts和@Signature注解明确拦截目标,否则MyBatis无法识别拦截逻辑。

  • @Intercepts:包裹一个或多个@Signature,表示拦截的一组目标。
  • @Signature:定义单个拦截目标,包含3个属性:

type:被拦截的接口(如Executor、StatementHandler);

method:被拦截的方法名(如Executor的query、update);

args:被拦截方法的参数类型数组(需与接口方法参数完全一致,用于区分重载方法)。

配置拦截器:让 MyBatis 识别拦截器

方式 1:MyBatis 原生配置(mybatis-config.xml)

  
    
    
      
       
    
  
方式 2:Spring Boot 配置(通过 @Bean 注册)
@Configuration
public class MyBatisConfig {
    @Bean
    public SqlExecuteTimeInterceptor sqlExecuteTimeInterceptor() {
        SqlExecuteTimeInterceptor interceptor = new SqlExecuteTimeInterceptor();
        // 设置参数
        Properties properties = new Properties();
        properties.setProperty("slowSqlThreshold", "500");
        interceptor.setProperties(properties);
        return interceptor;
    }
}

实战案例

动态修改 SQL(如数据权限控制)

对多租户系统,自动在SQL中添加租户ID条件(如where tenant_id = 123),避免手动编写。

@Override
public Object intercept(Invocation invocation) throws Throwable {
    // 获取StatementHandler及原始SQL
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
    String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
    // 获取当前租户ID(从ThreadLocal或登录上下文获取)
    String tenantId = TenantContext.getCurrentTenantId(); // 自定义上下文类

    // 拼接租户条件(简单示例:仅对SELECT语句处理)
    if (originalSql.trim().toLowerCase().startsWith("select") && tenantId != null) {
        String modifiedSql = originalSql + " and tenant_id = " + tenantId;
        // 修改SQL
        metaObject.setValue("delegate.boundSql.sql", modifiedSql);
    }

    return invocation.proceed(); // 执行修改后的SQL
}
参数加密与解密

对敏感参数(如手机号、身份证号)在入库前加密,查询时解密。

@Override
public Object intercept(Invocation invocation) throws Throwable {
    ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
    MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
    // 获取参数对象(如User对象)
    Object parameter = metaObject.getValue("parameterObject");
    if (parameter instanceof User) {
        User user = (User) parameter;
        // 加密手机号
        if (user.getPhone() != null) {
            user.setPhone(EncryptUtil.encrypt(user.getPhone())); // 自定义加密工具
        }
    }
    return invocation.proceed(); // 执行参数设置
}

注意事项

避免过度拦截,控制拦截范围

拦截器会嵌入SQL执行流程,过多或过频繁的拦截会增加性能开销(尤其是query、prepare等高频方法)。建议:

  • 仅拦截必要的接口和方法(如统计时间用Executor,改SQL用StatementHandler);
  • 避免在拦截逻辑中执行耗时操作(如IO、复杂计算)。

处理代理对象:获取原始对象

由于MyBatis会对目标接口生成代理,直接调用invocation.getTarget()可能得到代理对象(而非原始对象),需通过反射或MetaObject获取原始对象(如StatementHandler的delegate属性)。

推荐使用MyBatis提供的SystemMetaObject工具类处理反射,避免手动编写反射代码:

MetaObject metaObject = SystemMetaObject.forObject(target);
// 获取原始StatementHandler(delegate为StatementHandler代理的原始对象)
Object originalHandler = metaObject.getValue("delegate");

控制拦截器顺序:@Order 或配置顺序

若存在多个拦截器,执行顺序由注册顺序决定(先注册的先执行)。在Spring环境中,可通过@Order注解指定顺序(值越小越先执行):

@Order(1) // 第一个执行
public class SqlLogInterceptor implements Interceptor { ... }

@Order(2) // 第二个执行
public class SqlModifyInterceptor implements Interceptor { ... }

总结

MyBatis的SQL拦截器是其插件机制的核心,通过动态代理实现对SQL执行过程的灵活干预。本文从原理(四大接口、动态代理)、实现(定义拦截器、声明目标、配置生效)到实践(日志统计、SQL修改、参数加密),全面解析了拦截器的使用。

合理使用拦截器可以简化代码(如自动添加租户条件)、增强可观测性(如SQL日志),但需注意性能与兼容性。

本文地址:https://www.yitenyun.com/331.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 TIME_WAIT 运维 负载均衡 服务器 管理口 JumpServer SSL 堡垒机 跳板机 HTTPS HexHub Docker 服务器性能 JumpServer安装 堡垒机安装 Linux安装JumpServer SQL 查询 生命周期 Deepseek 宝塔面板 Linux宝塔 esxi esxi6 root密码不对 无法登录 web无法登录 锁机制 序列 核心机制 行业 趋势 Windows Windows server net3.5 .NET 安装出错 HTTPS加密 开源 PostgreSQL 存储引擎 Windows宝塔 Mysql重置密码 宝塔面板打不开 宝塔面板无法访问 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 机器学习 Redis Undo Log 机制 Oracle 处理机制 Spring 动态查询 无法访问宝塔面板 响应模型 优化 万能公式 InnoDB 数据库锁 连接控制 监控 异步化 Serverless 无服务器 语言 group by 索引 ES 协同 openHalo 工具 Postgres OTel Iceberg 技术 高可用 缓存方案 缓存架构 缓存穿透 分页查询 SVM Embedding scp Linux的scp怎么用 scp上传 scp下载 scp命令 存储 GreatSQL 连接数 数据 主库 国产数据库 Linux 安全 R edis 线程 日志文件 MIXED 3 SQLite-Web SQLite 数据库管理工具 R2DBC 加密 场景 Netstat Linux 服务器 端口 ​Redis 推荐模型 Recursive OB 单机版 自定义序列化 防火墙 黑客 启动故障 云原生 RocketMQ 长轮询 配置 向量数据库 大模型 共享锁 SQLark AI 助手 Canal Hash 字段 PG DBA 信息化 智能运维 不宕机 Rsync Ftp 磁盘架构 同城 双活 Python 向量库 Milvus 语句 分库 分表 架构 电商 系统 PostGIS 传统数据库 向量化 修改DNS Centos7如何修改DNS 业务 IT运维 redo log 重做日志 数据分类 流量 聚簇 非聚簇 线上 库存 预扣 filelock MongoDB 数据结构 MVCC Web 接口 开发 频繁 Codis MCP 开放协议 缓存 MySQL 9.3 数据类型 sftp 服务器 参数 失效 • 索引 • 数据库 mini-redis INCR指令 人工智能 推荐系统 千万级 大表 Redisson 锁芯 ZODB 工具链 Doris SeaTunnel 主从复制 代理 窗口 函数 EasyExcel MySQL8 prometheus Alert INSERT 数据备份 分布式架构 分布式锁​ 聚簇索引 非聚簇索引 高效统计 今天这篇文章就跟大家 崖山 新版本 事务 Java COMPACT SSH 虚拟服务器 虚拟机 内存 发件箱模式 分页 容器 MGR 分布式集群 数据脱敏 加密算法 核心架构 订阅机制 引擎 性能 Web 网络故障 QPS 高并发 网络架构 网络配置 RDB AOF 速度 服务器中毒 Go 数据库迁移 自动重启 容器化 播客 分布式 集中式 数据页 数据集成工具 读写 Redka B+Tree ID 字段 Redis 8.0 Valkey Valkey8.0 模型 OAuth2 Token StarRocks 数据仓库 微软 SQL Server AI功能 SpringAI Entity 关系数据库 池化技术 连接池 Caffeine CP DBMS 管理系统 JOIN 排行榜 排序 原子性 数据字典 兼容性 事务隔离 LRU Weaviate 业务场景 Testcloud 云端自动化 网络 dbt 数据转换工具 分页方案 排版 部署 1 事务同步 Pottery ReadView InfluxDB 优化器 意向锁 记录锁 AIOPS UUID ID 日志 sqlmock 悲观锁 乐观锁 单点故障 UUIDv7 主键 仪表盘 Pump 编程 单线程 Order Crash 代码 RAG HelixDB 对象 Ansible 分布式锁 Zookeeper 产业链 恢复数据 订单 字典 IT 双引擎 拦截器 动态代理 List 类型 LLM 线程安全 国产 用户 慢SQL优化 count(*) count(主键) 行数 表空间 快照读 当前读 视图 Next-Key 解锁 调优 RR 互联网 GitHub Git 神经系统 矢量存储 数据库类型 AI代理 CAS 查询规划 算法 技巧 多线程 闪回 并发控制 恢复机制