MyBatis拦截器原理与实战:SQL审计与性能优化

发布时间:2026/7/29 15:22:18
MyBatis拦截器原理与实战:SQL审计与性能优化 1. MyBatis拦截器深度解析在ORM框架使用过程中我们经常需要对SQL执行过程进行监控或干预。MyBatis拦截器Interceptor正是为此而生的利器它允许我们在执行SQL语句前后插入自定义逻辑。作为一名长期使用MyBatis的开发者我发现拦截器在审计日志、数据权限控制、SQL改写等方面有着不可替代的作用。2. MyBatis拦截器核心机制2.1 拦截器接口与实现原理MyBatis拦截器基于JDK动态代理实现核心接口是org.apache.ibatis.plugin.Interceptor。要实现一个拦截器必须实现以下三个方法public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }其中intercept方法是业务逻辑的核心入口plugin方法用于创建代理对象setProperties用于接收配置参数。MyBatis通过Intercepts和Signature注解来声明要拦截的目标方法。2.2 拦截点与执行时机MyBatis允许拦截四大核心接口的方法Executor (update, query, flushStatements等)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler (prepare, parameterize, batch, update, query)典型的拦截场景包括SQL执行耗时监控分页逻辑处理参数加密/解密结果集二次处理多租户数据过滤3. 实战构建SQL审计拦截器3.1 基础实现步骤下面是一个记录SQL执行日志的完整示例Intercepts({ Signature(type Executor.class, methodupdate, args{MappedStatement.class,Object.class}), Signature(type Executor.class, methodquery, args{MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}) }) public class SqlAuditInterceptor implements Interceptor { private static final Logger logger LoggerFactory.getLogger(SqlAuditInterceptor.class); Override public Object intercept(Invocation invocation) throws Throwable { long start System.currentTimeMillis(); Object result invocation.proceed(); long end System.currentTimeMillis(); MappedStatement ms (MappedStatement) invocation.getArgs()[0]; Object parameter invocation.getArgs()[1]; String sql ms.getBoundSql(parameter).getSql(); logger.info(执行SQL: {}, 耗时: {}ms, sql, end - start); return result; } // 其他必要方法实现... }3.2 关键配置要点在mybatis-config.xml中配置拦截器plugins plugin interceptorcom.example.SqlAuditInterceptor property namelogLevel valueDEBUG/ /plugin /plugins重要提示拦截器顺序会影响执行效果MyBatis按照配置顺序依次创建代理链4. 高级应用场景4.1 分页拦截器实现分页是拦截器的典型应用场景。以下是核心实现逻辑Override public Object intercept(Invocation invocation) throws Throwable { // 获取分页参数 Object parameter invocation.getArgs()[1]; PageParam pageParam getPageParam(parameter); if(pageParam ! null) { // 修改原始SQL String originalSql getBoundSql(invocation).getSql(); String pageSql buildPageSql(originalSql, pageParam); resetSql(invocation, pageSql); // 执行count查询 if(pageParam.isNeedCount()) { int total queryTotalCount(invocation, originalSql); pageParam.setTotal(total); } } return invocation.proceed(); }4.2 数据权限控制通过拦截器可以实现行级数据过滤String originalSql boundSql.getSql(); String whereClause buildDataPermissionWhere(user); String newSql originalSql WHERE whereClause; resetSql(invocation, newSql);5. 性能优化与问题排查5.1 常见性能陷阱过度拦截不要拦截不必要的方法特别是高频调用的方法复杂逻辑拦截器中避免耗时操作如远程调用内存泄漏注意拦截器实例的生命周期管理5.2 典型问题排查问题现象拦截器未生效检查点是否在mybatis-config.xml中正确配置Signature注解的type/method/args是否匹配目标方法是否被后续拦截器覆盖问题现象SQL修改未生效检查BoundSql的修改方式是否正确确认是否在正确的拦截点通常StatementHandler.prepare6. 安全注意事项6.1 SQL注入防护当拦截器需要修改SQL时要特别注意// 不安全做法 String newSql SELECT * FROM user WHERE name param ; // 安全做法 String newSql SELECT * FROM user WHERE name #{name};6.2 敏感数据处理对于敏感数据可以在拦截器中实现自动加解密Override public Object intercept(Invocation invocation) throws Throwable { Object parameter invocation.getArgs()[1]; decryptFields(parameter); // 参数解密 Object result invocation.proceed(); encryptFields(result); // 结果加密 return result; }7. 最佳实践总结单一职责每个拦截器只处理一个特定功能明确边界避免在拦截器中实现业务逻辑性能监控为关键拦截器添加耗时统计配置化通过properties使拦截器行为可配置文档完善为每个拦截器编写使用说明和示例在实际项目中我通常会建立一个拦截器基础库包含以下常用实现SQL审计日志慢查询监控数据权限过滤多租户隔离字段自动填充敏感数据加解密这些拦截器通过组合使用可以满足大部分企业级应用的特殊需求同时保持业务代码的整洁性。