统一拦截所有 SQL 查询的 AspectJ 实战指南

小静酱_5157

小静酱_5157

2026-08-01

959人浏览

原创

统一拦截所有 SQL 查询的 AspectJ 实战指南

本文介绍如何通过 AspectJ 织入 JDBC 底层接口(Statement 和 PreparedStatement),实现跨框架(JDBC、Spring JDBC、Hibernate、Spring Data JPA)的 SQL 全链路监控,无需依赖具体 ORM 实现。

本文介绍如何通过 aspectj 织入 jdbc 底层接口(`statement` 和 `preparedstatement`),实现跨框架(jdbc、spring jdbc、hibernate、spring data jpa)的 sql 全链路监控,无需依赖具体 orm 实现。

要真正实现“拦截所有 SQL 查询”,关键在于面向 JDBC 规范本身进行切面织入,而非依赖上层框架(如 JdbcTemplate 或 Repository 接口)的方法签名。因为无论 Spring Data JPA 还是 MyBatis,最终都会通过 java.sql.Statement 或其子接口(尤其是 PreparedStatement)执行 SQL——这是 JDBC 驱动层的统一出口。

✅ 正确的切入点设计

原始方案仅切 target(java.sql.Statement) 并捕获 args(sql),存在两大缺陷:

  • PreparedStatement 的 execute()/executeQuery() 等方法不接收 SQL 字符串作为参数(SQL 已在 prepareStatement(String sql) 时绑定),因此 args(sql) 匹配失败;
  • PreparedStatement.toString() 默认不返回原始 SQL(多数驱动返回内部对象描述),需调用 toString() 的增强实现或反射获取实际 SQL(见下文注意事项)。

改进后的切点应覆盖两类核心执行行为:

@Pointcut("target(java.sql.Statement)")
public void statement() {}

@Pointcut("target(java.sql.PreparedStatement)")
public void preparedStatement() {}

// 拦截 Statement.execute* 方法(如 executeQuery("SELECT ..."))
@AfterReturning(
    pointcut = "!within(AnalyzerAspect) && statement() && args(sql)",
    returning = "sql"
)
public void onStatementExecution(JoinPoint jp, String sql) {
    processSql(sql);
}

// 拦截 PreparedStatement.execute* 方法(无 SQL 参数,需从目标对象提取)
@AfterReturning(
    pointcut = "!within(AnalyzerAspect) && preparedStatement()",
    returning = "ret"
)
public void onPreparedStatementExecution(JoinPoint jp, Object ret) {
    if (!jp.getSignature().getName().startsWith("execute")) {
        return;
    }
    PreparedStatement ps = (PreparedStatement) jp.getTarget();
    String sql = extractSqlFromPreparedStatement(ps);
    processSql(sql);
}

? 关键:从 PreparedStatement 提取真实 SQL

不同 JDBC 驱动对 PreparedStatement.toString() 的实现各异。为可靠获取 SQL,推荐以下策略(按优先级):

Typewise.app
Typewise.app

一款面向客服和销售团队的 AI 写作辅助工具,通过智能文字建议和自动化回复能力帮助工作人员更快处理客户沟通内容。

下载
  1. 使用驱动特有 API(推荐)
    如 PostgreSQL 驱动支持 ((PgPreparedStatement) ps).getPreparedQuery().getSql();MySQL Connector/J 8+ 可通过反射访问 cachedSql 字段。

  2. 通用兜底方案(反射 + 兼容性处理)

    private String extractSqlFromPreparedStatement(PreparedStatement ps) {
        try {
            // 尝试标准 JDBC 4.2+ 的 getMetaData().getURL() 无帮助,改用反射
            Field field = ps.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            Object sqlObj = field.get(ps);
            return sqlObj != null ? sqlObj.toString() : ps.toString();
        } catch (Exception e) {
            // 回退到 toString(),并记录警告
            log.warn("Failed to extract SQL from PreparedStatement: {}", ps.toString(), e);
            return ps.toString();
        }
    }
  3. 启用驱动日志(开发/测试阶段)
    如 HikariCP + PostgreSQL 可配置 logLevel=DEBUG,辅助验证切面是否命中。

⚙️ Maven 配置要点

原配置中织入 spring-jdbc 和 hibernate-core 是无效且冗余的——这些库不包含 Statement 实现,真正需织入的是JDBC 驱动本身(如 postgresql、mysql-connector-java):

<weavedependencies><weavedependency><groupid>org.postgresql</groupid><artifactid>postgresql</artifactid></weavedependency><!-- 若使用 MySQL,替换为 --><!-- <groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> --></weavedependencies>

✅ 原因:PreparedStatement 的 executeQuery() 方法实现在驱动 JAR 中(如 org.postgresql.jdbc.PgPreparedStatement),只有织入驱动字节码,才能拦截其执行逻辑。

? 注意事项与最佳实践

  • 避免循环织入:!within(AnalyzerAspect) 是必须的,防止切面自身方法被重复拦截导致 StackOverflow。
  • 性能敏感场景慎用反射:生产环境建议针对主流驱动(PostgreSQL/MySQL/H2)做白名单优化,跳过反射。
  • 事务上下文关联:可通过 TransactionSynchronizationManager 获取当前事务 ID,将 SQL 日志与事务绑定,便于追踪。
  • SQL 脱敏处理:在 processSql() 中自动过滤 password=、token= 等敏感关键词,符合安全规范。
  • 兼容性验证:务必在目标 JDK 版本(如 Java 17+)和驱动版本下测试 aspectj-maven-plugin 的织入效果,避免因模块化(JPMS)导致类加载问题。

通过以上方案,你的 db-analyzer 库即可真正解耦于任何数据访问框架,稳定捕获所有经 JDBC 执行的 SQL,成为数据库操作的“透明观察者”。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
mybatis一级缓存和二级缓存
mybatis一级缓存和二级缓存

在MyBatis中,一级缓存和二级缓存是两种不同级别的缓存机制,它们都可以用来提高性能。本专题提供mybatis一级缓存和二级缓存相关文章,大家可以免费阅读。

2023.08.21

583

4

ibatis和mybatis有什么区别
ibatis和mybatis有什么区别

ibatis和mybatis的区别:1、基本信息不同;2、开发时间不同;3、功能与易用性;4、配置文件;5、入参类型与出参类型;6、返回结果集接受方式;7、语法差异;8、数据库方言支持;9、插件支持;10、社区活跃度;11、全球化支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

2282

5

mybatis如何配置数据库连接
mybatis如何配置数据库连接

mybatis配置数据库连接的方法:1、指定数据源;2、配置事务管理器;3、配置类型处理器和映射器;4、使用环境元素;5、配置别名。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

446

5

mybatis工作原理及流程是什么
mybatis工作原理及流程是什么

mybatis工作原理及流程:1、配置文件;2、接口与映射;3、sql解析与生成;4、执行计划;5、结果处理;6、动态sql;7、缓存机制;8、插件;9、事务管理;10、日志与监控;11、扩展性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

292

4

hibernate和mybatis有哪些区别
hibernate和mybatis有哪些区别

hibernate和mybatis的区别:1、实现方式;2、性能;3、对象管理的对比;4、缓存机制。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

302

5

Java MyBatis框架
Java MyBatis框架

本专题专注于Java主流ORM框架MyBatis的应用,系统讲解SQL映射、动态SQL、结果映射、分页查询、缓存机制与多表关联等核心内容,并结合企业管理系统、电商平台和后台管理项目实战,帮助学员全面掌握高效的数据库持久层开发技能。

2025.08.26

5469

22

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

2026.09.30

120

10

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

2026.09.30

100

14

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

2026.09.30

80

12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习