• Druid LogFilter输出可执行的SQL


    配置

    测试代码:

    1. DruidDataSource dataSource = new DruidDataSource();
    2. dataSource.setUrl("xxx");
    3. dataSource.setUsername("xxx");
    4. dataSource.setPassword("xxx");
    5. dataSource.setFilters("slf4j");
    6. dataSource.setValidationQuery("SELECT 1");
    7. dataSource.setTestOnBorrow(true);
    8. dataSource.setTestWhileIdle(true);
    9. Connection connection = dataSource.getConnection();
    10. PreparedStatement stmt = connection.prepareStatement("select * from tb_order where id=?");
    11. stmt.setInt(1,1);
    12. stmt.execute();

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

    SQL占位符和参数分开打印

    配置输出可执行的SQL

    Java启动参数配置方式

    -Ddruid.log.stmt.executableSql=true

    logFilter参数直接配置:

    1. <bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
    2. <property name="statementExecutableSqlLogEnable" value="true" />
    3. </bean>

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

    打印出了可执行的SQL

    源码

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

    1. // DruidPooledPreparedStatement.execute
    2. public boolean execute() throws SQLException {
    3. checkOpen();
    4. incrementExecuteCount();
    5. transactionRecord(sql);
    6. oracleSetRowPrefetch();
    7. conn.beforeExecute();
    8. try {
    9. //这个stmt是一个StatementProxyImpl实例
    10. return stmt.execute();
    11. } catch (Throwable t) {
    12. errorCheck(t);
    13. throw checkException(t);
    14. } finally {
    15. conn.afterExecute();
    16. }
    17. }

    StatementProxyImpl.execute:

    1. // StatementProxyImpl.execute
    2. public boolean execute() throws SQLException {
    3. updateCount = null;
    4. lastExecuteSql = sql;
    5. lastExecuteType = StatementExecuteType.Execute;
    6. lastExecuteStartNano = -1L;
    7. lastExecuteTimeNano = -1L;
    8. //调用过滤器链的preparedStatement_execute
    9. firstResultSet = createChain().preparedStatement_execute(this);
    10. return firstResultSet;
    11. }

    FilterChainImpl.preparedStatement_execute:

    1. public boolean preparedStatement_execute(PreparedStatementProxy statement) throws SQLException {
    2. if (this.pos < filterSize) {
    3. // 调用过滤器的preparedStatement_execute方法
    4. return nextFilter().preparedStatement_execute(this, statement);
    5. }
    6. return statement.getRawObject().execute();
    7. }

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

    1. // FilterEventAdapter.preparedStatement_execute
    2. public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
    3. try {
    4. statementExecuteBefore(statement, statement.getSql());
    5. // 递归调用配置的所有过滤器的preparedStatement_execute方法,直到真正执行完statement
    6. boolean firstResult = chain.preparedStatement_execute(statement);
    7. // 在执行完statement后执行,这是个空方法,具体的逻辑在子类里面实现,即LogFilter
    8. this.statementExecuteAfter(statement, statement.getSql(), firstResult);
    9. return firstResult;
    10. } catch (SQLException error) {
    11. statement_executeErrorAfter(statement, statement.getSql(), error);
    12. throw error;
    13. } catch (RuntimeException error) {
    14. statement_executeErrorAfter(statement, statement.getSql(), error);
    15. throw error;
    16. } catch (Error error) {
    17. statement_executeErrorAfter(statement, statement.getSql(), error);
    18. throw error;
    19. }
    20. }

    LogFilter.statementExecuteAfter方法:

    1. protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
    2. // 打印可执行的SQL
    3. logExecutableSql(statement, sql);
    4. // 统计SQL执行的时间
    5. if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
    6. statement.setLastExecuteTimeNano();
    7. double nanos = statement.getLastExecuteTimeNano();
    8. double millis = nanos / (1000 * 1000);
    9. statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
    10. + millis + " millis. " + sql);
    11. }
    12. }

    LogFilter.logExecutableSql:

    1. private void logExecutableSql(StatementProxy statement, String sql) {
    2. // 没有配置输出可执行SQL,直接返回
    3. if ((!isStatementExecutableSqlLogEnable()) || !isStatementLogEnabled()) {
    4. return;
    5. }
    6. // 获取SQL参数数量
    7. int parametersSize = statement.getParametersSize();
    8. if (parametersSize == 0) {
    9. statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
    10. + sql);
    11. return;
    12. }
    13. // 获取SQL的参数,存到parameters中
    14. List<Object> parameters = new ArrayList<Object>(parametersSize);
    15. for (int i = 0; i < parametersSize; ++i) {
    16. JdbcParameter jdbcParam = statement.getParameter(i);
    17. parameters.add(jdbcParam != null
    18. ? jdbcParam.getValue()
    19. : null);
    20. }
    21. DbType dbType = DbType.of(statement.getConnectionProxy().getDirectDataSource().getDbType());
    22. // 最终由SQLUtils根据参数列表和预执行SQL,转换为可执行SQL
    23. String formattedSql = SQLUtils.format(sql, dbType, parameters, this.statementSqlFormatOption);
    24. // statementLog是个抽象方法,由具体的日志过滤器类实现(如Slf4jLogFilter)
    25. statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
    26. + formattedSql);
    27. }

    总结

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

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

  • 相关阅读:
    MYSQL高可用集群MHA架构
    基于企业知识图谱的企业关联关系挖掘
    EM@平面直线方程和直线位置关系判定条件
    Mac的nodejs npm run build 报ERR_OSSL_EVP_UNSUPPORTED彻底解决办法
    DT建模快速入门
    zabbix二级目录反代部署
    LeetCode每日一题(30. Substring with Concatenation of All Words)
    【语音去噪】谱减法+维纳滤波+卡尔曼滤波语音去噪【含Matlab源码 1881期】
    2024Selenium自动化常见问题!
    C# .NET6 Log4net输出日志
  • 原文地址:https://blog.csdn.net/fengbin2005/article/details/132736197