開發高效能 JDBC 應用非易事:驅動程式不會因程式緩慢拋出異常。本文整理經驗證的四大優化原則:善用 Metadata、精準取數、選對 API、管理連線與更新,並附代碼對比示範。
ResultSet.getMetaData() 較慢,應用程式應緩存元資料資訊,避免重複執行。
DatabaseMetaData.getTypeInfo() 等方法調用一次後即緩存依賴結果。
Metadata 方法傳入 null 或搜尋模式極耗時,增加網路流量。盡可能提供 non-null 參數。
// 差:驅動需掃描所有 Schema、所有類型
ResultSet rs = dbmd.getTables(null, null, "WSTable", null);
// 優:精準定位
ResultSet rs = dbmd.getTables("cat1", "johng", "WSTable", "TABLE");
第二種寫法讓驅動程式解析為「當前目錄下 johng schema 的 TABLE 類型 WSTable」,效率大幅提升。
getColumns()需確認表結構時,勿用 getColumns() (需預儲+執行+傳回結果集);改用 PreparedStatement.getMetaData():
// 案例 1:getColumns - 完整查詢流程
ResultSet rs = dbmd.getColumns(..., "UnknownTable", ...);
while (rs.next()) { String colName = rs.getString(4); }
// 案例 2:啞元查詢 + getMetaData - 僅解析查詢計畫
PreparedStatement ps = conn.prepareStatement("SELECT * FROM UnknownTable WHERE 1=0");
ResultSetMetaData rsmd = ps.getMetaData();
int cols = rsmd.getColumnCount();
int type = rsmd.getColumnType(n); // 直接取得完整欄位資訊
啞元查詢 WHERE 1=0 永不執行,無資料傳輸,僅解析中繼資料。無預儲支援的 DBMS 執行時也極快 (無資料存取)。
SELECT *,明確列出欄位長資料 (BLOB/CLOB) 跨網路傳輸極慢。若 Employees 表含員工照片,SELECT * 會強制傳輸照片資料即使程式只需姓名。
// 差
ResultSet rs = stmt.executeQuery("SELECT * FROM Employees WHERE SSID='999-99-2222'");
// 優
ResultSet rs = stmt.executeQuery("SELECT Name FROM Employees WHERE SSID='999-99-2222'");
setMaxRows(int):限制最大列數setMaxFieldSize(int):限制單一欄位最大位元組 (針對長資料)setFetchSize(int):調整提取區塊大小Schema 設計時優先選擇處理效率高的型別:整數 > 浮點數/實數。浮點數通常採壓縮格式儲存,需解壓轉換才能協定處理,額外開銷大。
許多驅動模擬可捲動游標,呼叫 rs.last()、rs.getRow() 會導致驅動遍歷網路移至末列。
替代方案:SELECT COUNT(*) 或迭代 ResultSet 計數。
?{call getCustName(12345)} 以字串傳遞,DB 需解析、校驗、轉型。
{call getCustName(?)} + cstmt.setLong(1, 12345) → JDBC 驅動構造原生型別網路封包,**直接以 RPC 方式呼叫**,避開 SQL 解析層,效能大增。
Statement vs PreparedStatement 選擇邏輯| 場景 | 最佳選擇 | 理由 |
|---|---|---|
| SQL 僅執行一次 | Statement | 單次網路往返 (預儲+執行合一) |
| SQL 重複執行 | PreparedStatement | 首次預儲開銷大,後續僅執行極快 |
| 使用 PreparedStatement 池 | 視複用機率決定 | 極低複用率用 Statement;可能複用用 PreparedStatement |
addBatch() / executeBatch()// 例 1:逐筆執行 - 101 次網路往返
for (int n=0; n<100; n++) {
ps.setString(name[n]); ps.setLong(id[n]); ps.setInt(salary[n]);
ps.executeUpdate();
}
// 例 2:批次執行 - 僅 2 次網路往返
for (int n=0; n<100; n++) {
ps.setString(name[n]); ps.setLong(id[n]); ps.setInt(salary[n]);
ps.addBatch();
}
ps.executeBatch();
批次命令雖佔用更多 DB CPU,但**大幅減少網路往返**,JDBC 效能最大增益來自於此。
WHERE ROWID=?),效能較慢;適用於客戶端記憶體不足無法快取時getObject() 最泛化、效能最差 (需額外型別映射)。
優化:getString(1), getLong(2), getInt(15) 替代 getString("foo")。
索引方式省去欄位名大小寫轉換與列表查找,百萬列級別效能差異顯著。
傳統方式:INSERT 後立即 SELECT rowid ... — 兩次網路往返、非唯一鍵值風險。
JDBC 3.0 新特性:
int count = stmt.executeUpdate(
"INSERT INTO LocalGeniusList (name) VALUES ('Karen')",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys(); // 直接取得唯一鍵值
單次往返、唯一性保證、即使表無主鍵亦可用,極大提升存取效能與靈活度。