JDBC 規範定義了標準化的驅動發現與連接建立流程:
DriverManager.registerDriver(new com.mysql.jdbc.Driver())DriverManager.getConnection(url)DriverManager 遍歷已註冊驅動,調用 driver.acceptsURL(url) 匹配driver.connect(url, props) 返回 ConnectionJDBMonitor 的核心策略:提供自定義 DBDriver 實現 java.sql.Driver,**搶先匹配** 含特定前綴的 URL,建立代理連接鏈。
public class DBDriver implements Driver {
private static final String PREFIX = "jdbc:jdbmonitor:";
public boolean acceptsURL(String url) {
return url != null && url.startsWith(PREFIX);
}
public Connection connect(String url, Properties info) throws SQLException {
// 1. 解析真實 URL 與 listenerconfig
String realUrl = extractRealUrl(url); // url=jdbc:mysql://...
String listenerConfig = extractListenerConfig(url); // listenerconfig=/config.xml
// 2. 載入真實驅動 (反射/ServiceLoader)
Driver realDriver = loadRealDriver(realUrl);
// 3. 建立真實連接
Connection realConn = realDriver.connect(realUrl, info);
// 4. 包裝為代理連接,注入監控邏輯
return new DBConnection(realConn, listenerConfig);
}
// Driver 介面其他方法...
}
DBDriver (java.sql.Driver)
└─ DBConnection (java.sql.Connection)
├─ createStatement() → DBStatement
├─ prepareStatement(sql) → DBPreparedStatement
├─ prepareCall(sql) → DBCallableStatement
├─ commit/rollback/close → 記錄事務邊界、連接生命週期
└─ getMetaData() → DBDatabaseMetaData
DBStatement (java.sql.Statement)
├─ executeQuery(sql) → 記錄 SQL、耗時、結果集行數 → 返回 DBResultSet
├─ executeUpdate(sql) → 記錄 SQL、耗時、影響行數
└─ executeBatch() → 記錄批次 SQL、累計耗時
DBPreparedStatement (java.sql.PreparedStatement)
├─ setXxx(index, value) → 記錄參數綁定值 (核心優勢:Log4j 做不到)
├─ executeQuery() → 記錄完整 SQL (含參數)、耗時
└─ executeUpdate() → 記錄完整 SQL (含參數)、耗時
| 模式 | 應用場景 | 收益 |
|---|---|---|
| 代理模式 | DBConnection/Statement/PreparedStatement 代理真實對象 | 透明攔截,零業務代碼變更 |
| 裝飾者模式 | 層層包裝增強功能 (參數記錄、耗時統計、結果集行數) | 單一職責,功能正交組合 |
| 工廠模式 | DBConnection 創建對應的 Statement/PreparedStatement 代理 | 封裝創建邏輯,確保代理鏈完整 |
| 觀察者/監聽器 | Listener 接口 (FileListener, SocketListener, ConsoleListener) | 輸出目標可插拔,支援多目標並行 |
| 異步處理 | 記錄事件放入無界隊列,專用執行緒批量寫入 | 業務執行緒零阻塞,高併發穩定 |
標準 JDBC PreparedStatement **不提供獲取已綁定參數的 API**。JDBMonitor 通過代理 setXxx() 方法,在內部維護 Map,執行時組裝完整 SQL:
public class DBPreparedStatement implements PreparedStatement {
private final PreparedStatement realStmt;
private final Map params = new HashMap<>();
public void setString(int index, String value) {
params.put(index, value);
realStmt.setString(index, value);
}
// 其他 setXxx 同理...
public ResultSet executeQuery() {
String sqlWithParams = buildSqlWithParams(realStmt.toString(), params);
long start = System.nanoTime();
try {
return new DBResultSet(realStmt.executeQuery(), sqlWithParams, start);
} finally {
recordExecution(sqlWithParams, System.nanoTime() - start);
}
}
private String buildSqlWithParams(String originalSql, Map params) {
// 簡易佔位符替換邏輯,實際產品需處理引號轉義、類型格式化
String sql = originalSql;
for (Map.Entry e : params.entrySet()) {
sql = sql.replaceFirst("\\?", formatParam(e.getValue()));
}
return sql;
}
}
此能力讓開發者看到 **真實執行的參數化 SQL**,而非帶佔位符的模板,極大提升調試效率。
listenerconfig=/path/config.xml:啟動時解析,定義 Listener 鏈、過濾規則、格式化模板。classpath:、file:、http: 多種資源協議。public class AuditListener implements DBListener {
public void onEvent(DBEvent event) {
if (event.getType() == EXECUTE_UPDATE) {
// 解析 SQL 類型、表名、影響行數
// 寫入審計表 / 發送 Kafka / 調用風控服務
}
}
}
配置文件註冊:<listener name="audit" class="com.my.AuditListener"/>
JDBMonitor 以 **<10 個核心類、<2000 行代碼** 實現了生產級 SQL 監控,其精髓在於:
這是「理解標準化協議內部機制、以最小代價實現最大觀測能力」的教科書級案例。