You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不修改原有查询方法 基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:42:19