誤區: 官方文檔常推薦 OCI (Type 2) 以獲「最佳性能」。
實測結論: Thin 驅動 (純 Java、直連 TCP) 在絕大多數場景下吞吐更高、延遲更低、部署更簡單 (無需 Oracle Client)。
jdbc:oracle:thin:@//host:1521/service_name
Connection conn = DriverManager.getConnection(url, user, pwd);
conn.setAutoCommit(false); // 關鍵:減少網絡往返、鎖持有時間
try {
// 批量業務邏輯
conn.commit();
} catch (Exception e) {
conn.rollback();
throw e;
} finally {
conn.setAutoCommit(true); // 歸還連接池前恢復
}
| 場景 | 推薦對象 | 理由 |
|---|---|---|
| 動態構建 SQL (列名/表名變量) | Statement | 字符串拼接簡單,無綁定開銷 |
| 一次性/低頻查詢 | Statement | 避免 PreparedStatement 首次解析開銷 |
| 高頻重複執行 (參數化) | PreparedStatement | 服務端游標緩存、解析樹復用、防注入 |
| 批量 DML | PreparedStatement | 支持 addBatch/executeBatch |
// 單引號轉義
public static String sqlEscape(String s) {
return s.replace("'", "''");
}
// 日期轉 TO_DATE 字面量
public static String toOracleDate(java.sql.Date d) {
return "TO_DATE('" + new SimpleDateFormat("yyyy-MM-dd").format(d) + "', 'YYYY-MM-DD')";
}
// 用法
String sql = "SELECT * FROM emp WHERE ename = '" + sqlEscape(name) + "'
AND hiredate > " + toOracleDate(startDate);
stmt.executeQuery(sql);
雖單次耗時可能略高於 Statement,但共享池解析復用率提升、硬解析劇減,是高併發系統穩定性基石。
例外: 客戶端工具/報表系統單用戶低併發場景。
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO batch_t (id, data) VALUES (?, ?)");
// 方式一:標準 JDBC 批處理 (跨庫通用)
for (int i=0; i<10000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "data"+i);
pstmt.addBatch();
}
pstmt.executeBatch(); // 一次網絡往返發送 10000 條
// 方式二:Oracle 專有批處理 (性能更強,需強轉)
((oracle.jdbc.OraclePreparedStatement)pstmt).setExecuteBatch(50); // 每 50 條自動刷新
for (int i=0; i<10000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "data"+i);
pstmt.executeUpdate(); // 內部累積批次
}
// 手動刷新剩餘批次
((oracle.jdbc.OraclePreparedStatement)pstmt).sendBatch();
實測: 專有批處理比標準批處理快 30%-50%,比單條 executeUpdate 快 10-20 倍。
Thin 驅動限制: 不支持 PreparedStatement.setBlob() / setClob() / setBinaryStream() / setCharacterStream() 直接寫入 LOB。
正確模式:
// 插入 CLOB
String sql = "INSERT INTO doc (id, content) VALUES (?, EMPTY_CLOB()) RETURNING content INTO ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1001);
pstmt.registerOutParameter(2, OracleTypes.CLOB);
pstmt.executeUpdate();
Clob clob = ((OracleCallableStatement)pstmt).getClob(2);
clob.setString(1, "超長文本內容..."); // Locator 寫入
pstmt.close();
// 讀取 BLOB
ResultSet rs = stmt.executeQuery("SELECT blob_data FROM doc WHERE id=1001");
if (rs.next()) {
Blob blob = rs.getBlob(1);
InputStream is = blob.getBinaryStream(); // Locator 讀取
// 處理流...
}
PreparedStatement pstmt = conn.prepareStatement(
"INSERT INTO t (name) VALUES (?)", new String[]{"ID"}); // 指定返回列
pstmt.setString(1, "test");
pstmt.executeUpdate();
ResultSet keys = pstmt.getGeneratedKeys();
if (keys.next()) {
long newId = keys.getLong(1);
}
keys.close();
# HikariCP 核心參數 (application.yml)
maximumPoolSize: 50 # 核心數 * 2 + 有效盤數
minimumIdle: 10
idleTimeout: 300000
maxLifetime: 1800000
connectionTimeout: 30000
leakDetectionThreshold: 60000
# Oracle 專用優化
dataSourceProperties:
oracle.net.CONNECT_TIMEOUT: 10000
oracle.net.READ_TIMEOUT: 30000
oracle.jdbc.defaultRowPrefetch: 100 # 預取行數,減少網絡往返
oracle.jdbc.useFetchSizeWithLongColumn: true
v$session.program: "MyApp-Module" # 便於 DB 端識別
// 開啟隱式 Statement Cache (Oracle JDBC 專有)
((oracle.jdbc.OracleConnection)conn).setImplicitCachingEnabled(true);
((oracle.jdbc.OracleConnection)conn).setStatementCacheSize(100);
// 效果:相同 SQL 文本的 PreparedStatement 自動緩存,再次 prepareStatement 直接從緩存獲取
// 監控:V$SQLAREA / V$SESSION_CACHED_CURSORS
總結: JDBC 優化的核心矛盾是網絡往返次數 vs 客戶端/服務端 CPU。
Thin 驅動 + 手動事務 + 批處理 + Locator LOB + 連接池調優 + Statement Cache,這六大支柱能解決 90% 的性能瓶頸。切記:過早優化是萬惡之源,先建立基準測試,再針對性調優。