生产环境无需堆转储查看JDBC Prepared Statements列表方法问询
如何在生产环境查看Java应用JDBC端的Prepared Statements列表(无需堆转储)
方法1:利用PostgreSQL JDBC驱动内部缓存(反射方式)
PostgreSQL JDBC驱动(pgjdbc)会在连接内部维护Prepared Statement缓存,可通过反射获取缓存中的SQL语句列表,适合快速排查:
import org.postgresql.jdbc.PgConnection; import java.sql.Connection; import java.lang.reflect.Field; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PreparedStmtLogger { private static final Logger logger = LoggerFactory.getLogger(PreparedStmtLogger.class); public static void logPreparedStatements(Connection conn) { if (!(conn instanceof PgConnection)) { logger.warn("Connection is not a PostgreSQL JDBC connection"); return; } PgConnection pgConn = (PgConnection) conn; try { // 获取驱动内部的PreparedStatement缓存(字段名随驱动版本可能变化) Field cacheField = PgConnection.class.getDeclaredField("preparedStatementCache"); cacheField.setAccessible(true); Map<String, ?> stmtCache = (Map<String, ?>) cacheField.get(pgConn); // 遍历缓存并写入日志 logger.info("Current JDBC Prepared Statement Cache (size: {})", stmtCache.size()); for (String sql : stmtCache.keySet()) { logger.info("Cached SQL: {}", sql); } } catch (NoSuchFieldException | IllegalAccessException e) { logger.error("Failed to access Prepared Statement cache", e); } } }
注意:该方式依赖驱动内部实现,不同pgjdbc版本的字段名可能变更,建议先确认对应版本的源码结构。
方法2:自定义PreparedStatement包装器(连接池拦截)
如果使用连接池(如HikariCP、Druid),可以通过包装Connection对象,拦截所有prepareStatement调用,记录创建的预编译语句:
以HikariCP为例,自定义连接包装类:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TrackingConnection implements Connection { private static final Logger logger = LoggerFactory.getLogger(TrackingConnection.class); private final Connection delegate; public TrackingConnection(Connection delegate) { this.delegate = delegate; } // 拦截prepareStatement方法,记录SQL @Override public PreparedStatement prepareStatement(String sql) throws SQLException { logger.info("Created Prepared Statement: {}", sql); return delegate.prepareStatement(sql); } // 实现Connection接口的所有其他方法,全部委托给delegate @Override public void close() throws SQLException { delegate.close(); } // 其余方法(如createStatement、prepareCall等)请通过IDE自动生成委托实现 }
然后在连接池配置中添加拦截逻辑,比如HikariCP可以通过自定义HikariDataSource的setConnectionInitSql或扩展PoolEntryCreator,将原生Connection包装为TrackingConnection。
方法3:通过JMX监控获取
多数连接池和pgjdbc驱动支持JMX暴露监控指标,可通过JMX API获取Prepared Statement缓存信息:
import javax.management.MBeanServer; import javax.management.ObjectName; import java.lang.management.ManagementFactory; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class JmxStmtMonitor { private static final Logger logger = LoggerFactory.getLogger(JmxStmtMonitor.class); public static void logStmtCacheStats() { MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); try { // 查询pgjdbc的连接池MBean Set<ObjectName> connPoolBeans = mBeanServer.queryNames( new ObjectName("org.postgresql:type=ConnectionPool,*"), null ); for (ObjectName beanName : connPoolBeans) { Integer cacheSize = (Integer) mBeanServer.getAttribute(beanName, "PreparedStatementCacheSize"); logger.info("Connection Pool {} - Prepared Statement Cache Size: {}", beanName, cacheSize); // 部分驱动版本可能支持获取缓存的SQL列表,可尝试获取对应属性 } // 若使用HikariCP,可查询其MBean获取缓存命中率等信息 Set<ObjectName> hikariBeans = mBeanServer.queryNames( new ObjectName("com.zaxxer.hikari:type=Pool,*"), null ); for (ObjectName beanName : hikariBeans) { Double stmtCacheHitRate = (Double) mBeanServer.getAttribute(beanName, "PreparedStatementCacheHitRatio"); logger.info("Hikari Pool {} - Prepared Statement Cache Hit Rate: {:.2%}", beanName, stmtCacheHitRate); } } catch (Exception e) { logger.error("Failed to fetch JMX stats", e); } } }
生产环境也可直接用JConsole、VisualVM连接应用的JMX端口,实时查看缓存状态,无需修改代码。
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

