java中jdbc打印sql耗时日志的核心是拦截执行、记录起止时间并计算差值,常用方式有:一、jdk动态代理包装statement;二、封装工具类显式包裹;三、引入p6spy零代码改造。

在 Java 中使用 JDBC 打印 SQL 执行耗时日志,核心思路是**拦截 SQL 执行过程、记录开始和结束时间、计算差值并输出日志**。JDBC 本身不提供内置日志耗时功能,需借助代理(如 Proxy)、封装工具类或第三方库(如 P6Spy、log4jdbc)实现。下面介绍几种实用、轻量、无需引入大依赖的方式。
方式一:用 JDK 动态代理包装 Statement/PreparedStatement
这是最通用、可控性最强的纯 JDBC 方案,适用于自研连接池或直接使用 DriverManager 的场景。
- 对
Connection获取的Statement或PreparedStatement创建代理对象 - 在
executeQuery()、executeUpdate()、execute()等方法前后记录纳秒级时间戳 - 捕获异常时也记录耗时,并标记“失败”
- 日志内容建议包含:SQL(参数已填充或带占位符)、耗时(ms)、线程名、是否成功
示例关键代码片段:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
TimingStatementHandler 类Statement stmt = connection.createStatement();
Statement proxyStmt = (Statement) Proxy.newProxyInstance(
stmt.getClass().getClassLoader(),
stmt.getClass().getInterfaces(),
(proxy, method, args) -> {
if ("executeQuery".equals(method.getName()) ||
"executeUpdate".equals(method.getName()) ||
"execute".equals(method.getName())) {
long start = System.nanoTime();
try {
Object result = method.invoke(stmt, args);
long costMs = (System.nanoTime() - start) / 1_000_000;
log.info("SQL executed in {} ms: {}", costMs, getSqlFromArgs(args));
return result;
} catch (Exception e) {
long costMs = (System.nanoTime() - start) / 1_000_000;
log.warn("SQL failed after {} ms: {}", costMs, getSqlFromArgs(args), e);
throw e;
}
}
return method.invoke(stmt, args);
}
);
方式二:封装统一的 JDBC 工具类(推荐初学者)
不依赖代理,通过显式调用封装方法来包裹 SQL 执行逻辑,简单清晰,适合小项目或教学场景。
- 提供
queryWithTime()、updateWithTime()等静态方法 - 方法内部完成:获取 Statement → 设置参数 → 记录开始时间 → 执行 → 计算耗时 → 输出日志 → 返回结果
- SQL 参数建议用
String.format或简单替换模拟填充(仅用于日志,不代替 PreparedStatement 防注入)
例如:
public static <t> T queryWithTime(Connection conn, String sql, Function<resultset t> mapper) throws SQLException {
long start = System.currentTimeMillis();
try (PreparedStatement ps = conn.prepareStatement(sql)) {
// 此处可加 setXXX 赋值逻辑(若需)
try (ResultSet rs = ps.executeQuery()) {
T result = mapper.apply(rs);
long cost = System.currentTimeMillis() - start;
log.debug("QUERY [{}] took {} ms", sql, cost);
return result;
}
}
}</resultset></t>
方式三:用 P6Spy 快速启用(零代码修改)
如果项目允许引入轻量依赖(仅 ~200KB),P6Spy 是最省心的选择,它作为 JDBC 驱动的代理层,自动记录所有 SQL 及耗时,无需改业务代码。
- Maven 引入:
p6spy:p6spy:3.9.1 - 修改
jdbc.url:把原驱动类(如com.mysql.cj.jdbc.Driver)换成com.p6spy.engine.spy.P6SpyDriver - 配置
spy.properties,开启executionThreshold(如设为 100,只记 >100ms 的慢 SQL)和modulelist=timed - 日志自动输出类似:
17:23:45.123 | 245ms | SELECT * FROM user WHERE id = ?
补充提醒:避免常见坑
-
别用
System.currentTimeMillis()测短耗时——精度不够,优先用System.nanoTime() -
不要在日志里直接拼接未处理的 SQL 参数——易被注入或日志爆炸;建议用占位符或脱敏(如
WHERE name = ?) -
连接获取、事务开启/提交也耗时——如需全链路耗时,需从
getConnection()开始计时 - 生产环境慎开全量 SQL 日志——高频小查询会刷爆磁盘,建议配合阈值(如 >100ms)或异步日志
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










