如何不修改原有查询方法 基于AspectJ获取JOOQ查询SQL文本
JOOQ场景下fetch()调用的无侵入AspectJ拦截方案
核心实现思路
JOOQ的fetch()方法虽然实现类为包私有,但方法定义在公开的org.jooq.ResultQuery接口上,AspectJ不需要感知具体实现类,直接匹配接口方法的调用点即可完成拦截,不需要修改任何原有业务查询代码。
注意:JOOQ的查询对象是动态构造的,不归Spring容器管理,不要使用仅支持容器代理的Spring AOP,需要启用原生AspectJ的编译期织入或加载期织入(LTW)能力。
具体实现代码
1. 切点定义
直接匹配ResultQuery接口的所有fetch重载方法的调用点,不需要指定具体实现类:
@Pointcut("call(* org.jooq.ResultQuery.fetch(..))") public void jooqFetchExecution() {}
如果只需要拦截无参的fetch()方法,把切点里的fetch(..)改成fetch()即可。
2. 环绕通知实现
拦截到调用后直接从连接点拿到查询实例,通过JOOQ原生API获取SQL和绑定参数,执行后续埋点/审计逻辑:
@Around("jooqFetchExecution()") public Object interceptJooqFetch(ProceedingJoinPoint pjp) throws Throwable { // 获取当前执行的JOOQ查询实例 ResultQuery<?> query = (ResultQuery<?>) pjp.getTarget(); // 获取带?占位符的原生SQL String executeSql = query.getSQL(); // 获取SQL对应的绑定参数列表 List<Object> bindParams = query.getBindValues(); // 复用现有埋点逻辑,比如记录执行开始时间 long startTs = System.currentTimeMillis(); Object fetchResult; try { fetchResult = pjp.proceed(); } finally { long cost = System.currentTimeMillis() - startTs; // 这里可以接入你现有的HPQL/JDBC统一埋点逻辑,比如上报SQL、耗时、参数等 System.out.printf("JOOQ execute sql: %s, params: %s, cost: %dms%n", executeSql, bindParams, cost); } return fetchResult; }
避坑说明
- 禁止使用
target(AbstractResultQuery)、target(SelectImpl)这类匹配包私有实现类的切点写法:这类类的访问权限不允许跨包匹配,且不同JOOQ版本的类名、包路径可能变动,兼容性极差。 - 切点优先使用
call()类型而非execution():call()匹配的是业务代码中调用fetch()的位置,不需要感知底层方法执行的实现类,不受实现类可见性影响,匹配逻辑更稳定。
备选无侵入方案(无AOP依赖)
如果项目启用AspectJ织入有困难,可以直接用JOOQ官方提供的ExecuteListener SPI扩展点,只需要在构造DSLContext的配置类中注册自定义监听器,即可在SQL执行的全生命周期拿到语句和参数,完全不需要修改业务代码,稳定性比切面更高:
// 自定义执行监听器 public class SqlRecordListener extends DefaultExecuteListener { @Override public void executeStart(ExecuteContext ctx) { String sql = ctx.sql(); // 这里做SQL记录、埋点逻辑即可 } } // 注册到JOOQ配置 Configuration configuration = new DefaultConfiguration() .set(connectionProvider) .set(SQLDialect.MYSQL) .set(new SqlRecordListener()); DSLContext context = DSL.using(configuration);
内容的提问来源于stack exchange,提问作者Mary
相关产品推荐
相关产品推荐

