Druid LogFilter输出可执行的SQL

配置

测试代码:

复制代码
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("xxx");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
dataSource.setFilters("slf4j");
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestOnBorrow(true);
dataSource.setTestWhileIdle(true);

Connection connection = dataSource.getConnection();

PreparedStatement stmt = connection.prepareStatement("select * from tb_order where id=?");
stmt.setInt(1,1);
stmt.execute();

在配置输出可执行的SQL之前,看下Druid的日志输出:

SQL占位符和参数分开打印

配置输出可执行的SQL

Java启动参数配置方式

复制代码
-Ddruid.log.stmt.executableSql=true

logFilter参数直接配置:

复制代码
<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
        <property name="statementExecutableSqlLogEnable" value="true" />
  </bean>

在配置输出可执行的SQL之后,看下Druid的日志输出:

打印出了可执行的SQL

源码

调用栈是DruidPooledPreparedStatement.execute->StatementProxyImpl.execute-> FilterChainImpl.preparedStatement_execute->FilterEventAdapter.preparedStatement_execute(所有Filter都继承了FilterEventAdapter)-> LogFilter.statementExecuteAfter->LogFilter.logExecutableSql。源码如下:

复制代码
// DruidPooledPreparedStatement.execute
public boolean execute() throws SQLException {
    checkOpen();

    incrementExecuteCount();
    transactionRecord(sql);

    oracleSetRowPrefetch();

    conn.beforeExecute();
    try {
        //这个stmt是一个StatementProxyImpl实例
        return stmt.execute();
    } catch (Throwable t) {
        errorCheck(t);

        throw checkException(t);
    } finally {
        conn.afterExecute();
    }
}

StatementProxyImpl.execute:

复制代码
// StatementProxyImpl.execute
public boolean execute() throws SQLException {
    updateCount = null;
    lastExecuteSql = sql;
    lastExecuteType = StatementExecuteType.Execute;
    lastExecuteStartNano = -1L;
    lastExecuteTimeNano = -1L;
    //调用过滤器链的preparedStatement_execute
    firstResultSet = createChain().preparedStatement_execute(this);
    return firstResultSet;
}

FilterChainImpl.preparedStatement_execute:

复制代码
public boolean preparedStatement_execute(PreparedStatementProxy statement) throws SQLException {
    if (this.pos < filterSize) {
        // 调用过滤器的preparedStatement_execute方法
        return nextFilter().preparedStatement_execute(this, statement);
    }
    return statement.getRawObject().execute();
}

所有过滤器都继承了FilterEventAdapter,看名字是个和事件有关的类,这个父类里面实现了preparedStatement_execute方法:

复制代码
// FilterEventAdapter.preparedStatement_execute
public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
    try {
        statementExecuteBefore(statement, statement.getSql());
        // 递归调用配置的所有过滤器的preparedStatement_execute方法,直到真正执行完statement
        boolean firstResult = chain.preparedStatement_execute(statement);
        // 在执行完statement后执行,这是个空方法,具体的逻辑在子类里面实现,即LogFilter
        this.statementExecuteAfter(statement, statement.getSql(), firstResult);

        return firstResult;

    } catch (SQLException error) {
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (RuntimeException error) {
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    } catch (Error error) {
        statement_executeErrorAfter(statement, statement.getSql(), error);
        throw error;
    }

}

LogFilter.statementExecuteAfter方法:

复制代码
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
    // 打印可执行的SQL
    logExecutableSql(statement, sql);
    // 统计SQL执行的时间
    if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
        statement.setLastExecuteTimeNano();
        double nanos = statement.getLastExecuteTimeNano();
        double millis = nanos / (1000 * 1000);

        statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                     + millis + " millis. " + sql);
    }
}

LogFilter.logExecutableSql:

复制代码
private void logExecutableSql(StatementProxy statement, String sql) {
    // 没有配置输出可执行SQL,直接返回
    if ((!isStatementExecutableSqlLogEnable()) || !isStatementLogEnabled()) {
        return;
    }
    // 获取SQL参数数量
    int parametersSize = statement.getParametersSize();
    if (parametersSize == 0) {
        statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                     + sql);
        return;
    }
    // 获取SQL的参数,存到parameters中
    List<Object> parameters = new ArrayList<Object>(parametersSize);
    for (int i = 0; i < parametersSize; ++i) {
        JdbcParameter jdbcParam = statement.getParameter(i);
        parameters.add(jdbcParam != null
                ? jdbcParam.getValue()
                : null);
    }

    DbType dbType = DbType.of(statement.getConnectionProxy().getDirectDataSource().getDbType());
    // 最终由SQLUtils根据参数列表和预执行SQL,转换为可执行SQL
    String formattedSql = SQLUtils.format(sql, dbType, parameters, this.statementSqlFormatOption);
    // statementLog是个抽象方法,由具体的日志过滤器类实现(如Slf4jLogFilter)
    statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                 + formattedSql);
}

总结

输出可执行SQL在日常排查SQL执行错误还是很实用的。其原理是在PreparedStatement.execute执行之后,调用SQLUtils.format打印出可执行的SQL。FilterEventAdapter这个类很关键,它会在SQL执行之前或者之后,调用扩展的处理,具体的处理逻辑又委派给子类实现。

Springboot Druid配置可执行sql配置_druid 执行sql_孙陆泉的博客-CSDN博客

相关推荐
豆沙沙包?5 分钟前
2025年--Lc197-077. 排序链表(链表,尾插法)--Java版
java·数据结构·链表
m0_6515939111 分钟前
深入理解软件设计中的协议与规范:从理论到Java实践
java·软件工程·代码规范·设计规范
Knight_AL21 分钟前
Tomcat 类加载器隔离机制的实际应用
java·tomcat
FreeBuf_24 分钟前
Spring两大漏洞可导致泄露敏感信息及安全防护绕过(CVE-2025-41253/41254)
java·安全·spring
江太翁32 分钟前
Kotlin 与 Java 互操作中常用注解
java·python·kotlin
notillusion36 分钟前
URR#69598
java·php·程序优化
notillusion1 小时前
CUK#22162
java·php·程序优化
RainbowSea1 小时前
11. Spring AI + ELT
java·spring·ai编程
李慕婉学姐1 小时前
Springboot黄河文化科普网站5q37v(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
RainbowSea1 小时前
12. 模型RAG评测
java·spring·ai编程