配置
测试代码:
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_executefirstResultSet = 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方法,直到真正执行完statementboolean firstResult = chain.preparedStatement_execute(statement);// 在执行完statement后执行,这是个空方法,具体的逻辑在子类里面实现,即LogFilterthis.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) {// 打印可执行的SQLlogExecutableSql(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,转换为可执行SQLString 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博客