如何在Spring Boot中记录SQL查询耗时?
解决方案:Spring/Hikari内置实现SQL耗时统计
一、利用Spring JDBC的StatementInterceptor拦截SQL执行
Spring JDBC原生提供StatementInterceptor接口,可在SQL执行前后插入逻辑,正好用来统计耗时,还能结合Log4j2的开关灵活控制。
实现步骤:
- 自定义拦截器类:
import org.springframework.jdbc.core.StatementInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.Properties; public class SqlTimingInterceptor implements StatementInterceptor { private static final Logger logger = LoggerFactory.getLogger("SqlTimingLogger"); @Override public Statement intercept(Statement statement, Connection connection) { String sql = null; // 尝试从PreparedStatement中解析SQL语句(兼容不同JDBC驱动格式) if (statement instanceof PreparedStatement) { try { sql = ((PreparedStatement) statement).toString().split(": ")[1]; } catch (Exception e) { sql = "无法解析SQL内容"; } } return new TimingStatement(statement, sql); } @Override public void setProperties(Properties properties) {} // 包装Statement,记录执行前后时间差 private static class TimingStatement extends StatementWrapper { private final String sql; private long startTime; public TimingStatement(Statement delegate, String sql) { super(delegate); this.sql = sql; } @Override public boolean execute(String sql) throws SQLException { startTime = System.currentTimeMillis(); try { return super.execute(sql); } finally { logExecutionTime(sql); } } @Override public int executeUpdate(String sql) throws SQLException { startTime = System.currentTimeMillis(); try { return super.executeUpdate(sql); } finally { logExecutionTime(sql); } } @Override public java.sql.ResultSet executeQuery(String sql) throws SQLException { startTime = System.currentTimeMillis(); try { return super.executeQuery(sql); } finally { logExecutionTime(sql); } } @Override public boolean execute() throws SQLException { startTime = System.currentTimeMillis(); try { return super.execute(); } finally { logExecutionTime(this.sql); } } private void logExecutionTime(String sql) { if (logger.isDebugEnabled()) { long elapsed = System.currentTimeMillis() - startTime; logger.debug("SQL执行耗时: {}ms | SQL内容: {}", elapsed, sql); } } } }
- 配置到Spring的
JdbcTemplate:
- Java配置方式:
@Configuration public class JdbcConfig { @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setStatementInterceptors(new SqlTimingInterceptor()); return jdbcTemplate; } }
- XML配置方式:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="hikariDataSource"/> <property name="statementInterceptors"> <list> <bean class="com.yourpackage.SqlTimingInterceptor"/> </list> </property> </bean>
- Log4j2开关控制:
在log4j2.xml中配置对应日志级别,按需开启:
<Logger name="SqlTimingLogger" level="DEBUG" additivity="false"> <AppenderRef ref="YourAppender"/> </Logger>
二、HikariCP内置慢SQL日志(零代码配置)
HikariCP本身支持慢SQL记录,无需额外代码,只需配置参数即可:
Spring Boot环境配置示例:
在application.properties中添加:
# 慢SQL阈值,单位毫秒,超过该时长的SQL会被记录 spring.datasource.hikari.leak-detection-threshold=500 # 开启Hikari的DEBUG日志,输出连接池及慢SQL信息 logging.level.com.zaxxer.hikari=DEBUG
注:若仅需慢SQL记录,开启
leak-detection-threshold即可;DEBUG日志会输出更多连接池细节,可根据需求调整级别。
三、修复你之前的LogWriter方案
你之前的setLogWriter未生效,是因为HikariCP默认关闭了JDBC LogWriter功能,调整配置即可启用:
步骤:
- 开启Hikari的LogWriter配置:
spring.datasource.hikari.logWriterEnabled=true
- 修改代码确保操作HikariDataSource实例:
@PostConstruct public void setLogWriter() { final Logger logger = LoggerFactory.getLogger("no.bring.notifications.engine.JdbcStatements"); try { if (dataSource instanceof HikariDataSource) { HikariDataSource hikariDs = (HikariDataSource) dataSource; hikariDs.setLogWriter(new PrintWriter(System.out) { @Override public void println(String x) { if (logger.isDebugEnabled()) { logger.debug(x); } } }); } } catch (SQLException e) { logger.error("设置日志写入器失败", e); } }
此方案仅能输出Hikari连接池相关日志,无法直接统计SQL执行耗时,更推荐前两种方案。
内容的提问来源于stack exchange,提问作者oligofren
相关产品推荐
相关产品推荐

