在 WebLogic 中啟用 PreparedStatement Cache 後,應用若執行 DDL (如 ALTER TABLE),後續復用缓存的 PreparedStatement 會導致 Oracle 拋出 SQLException: 違反協議 (Protocol Violation)。本文深度剖析根因、驗證代碼、WebLogic 官方文檔解讀,並給出生產環境可落地的兩種解決方案。
Prepared Statement Cache Size > 0ALTER TABLE test ADD (col VARCHAR2(10)))PreparedStatement (如 SELECT * FROM test) 拋出:java.sql.SQLException: XA error: XAER_PROTO: Protocol violation 或 Unexpected exception while enlisting XAConnection核心結論:DDL 導致數據庫端游標/會話狀態變更,WebLogic 缓存的 PreparedStatement 綁定在 Physical Connection 上,而非邏輯連接。單純清理 Statement Cache **無效**,必須讓 Physical Connection 失效並重建。
PreparedStatement 時,仍復用同一 Physical Connection (池中對象)。public class OracleDriverTest {
public static void main(String[] args) {
try {
OracleDriver driver = (OracleDriver) Class.forName("oracle.jdbc.OracleDriver").newInstance();
DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:coffeedb", "system", "coffee");
PreparedStatement pstmt = conn.prepareStatement("select * from Test");
for (int loop = 0; loop < 10; loop++) {
try {
System.out.println(pstmt.toString());
ResultSet rs = pstmt.executeQuery();
while (rs.next()) System.out.println(rs.getString(1));
rs.close();
Thread.sleep(5000); // 此處手動執行 ALTER TABLE
} catch (SQLException se) {
se.printStackTrace();
System.out.println("get exception, remake prepared statement in loop: " + loop);
// 單純重建 Statement 無效
// pstmt = conn.prepareStatement("select * from Test");
// 必須重建 Physical Connection
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:coffeedb", "system", "coffee");
pstmt = conn.prepareStatement("select * from Test");
continue;
}
}
pstmt.close(); conn.close();
} catch (Exception e) { e.printStackTrace(); }
}
}
註釋說明:單純重建PreparedStatement(第 46 行註釋) 仍拋異常;必須重建Connection(第 45 行激活) 才能恢復。
WebLogic JDBC 文檔 (thirdparty.html) 明確指導:
SQLException 後,獲取 Vendor Connection (Physical Connection):java.sql.Connection vendorConn = ((WLConnection)conn).getVendorConnection();true:應用關閉邏輯連接時,**不將 Physical Connection 歸池**,而是關閉並重建新 Physical Connection 補充池。適用於調用 getVendorConnection() 後的場景。false (默認):關閉邏輯連接時,Physical Connection 歸池復用。若已調用 getVendorConnection() 可能導致池狀態不一致。啟用此項會影響性能 (禁用連接池復用),但能徹底解決 DDL 後協議違規問題。
public void executeWithDdlRecovery(Connection logicalConn, String sql) throws SQLException {
try (PreparedStatement pstmt = logicalConn.prepareStatement(sql)) {
pstmt.executeQuery();
} catch (SQLException e) {
// 1. 獲取 Vendor Connection (Physical)
WLConnection wlConn = (WLConnection) logicalConn;
Connection vendorConn = wlConn.getVendorConnection(); // oracle.jdbc.OracleConnection
// 2. 拋出異常,讓上層關閉邏輯連接
// 連接池因 RemoveInfectedConnectionsEnabled=true 會銷毀該 Physical Connection 並創建新的
throw e;
}
}
// 使用示例
Connection conn = dataSource.getConnection(); // 邏輯連接
try {
executeWithDdlRecovery(conn, "SELECT * FROM test");
} finally {
conn.close(); // 觸發連接池處理:銷毀舊 Physical Connection,創建新的
}
XA Transaction Timeout 大於全局事務超時,主動通知 RM 超時時間。getVendorConnection() 返回對象 **不可序列化** (Oracle T4CConnection),**僅能在 Server 端 (同 JVM) 使用**,不能傳遞到遠程客戶端。Remove Infected Connections 機制處理。vendorConn.close() 雖未報錯 (Oracle 驅動忽略重複關閉),但違反最佳實踐。Remove Infected Connections Enabled = true,捕獲異常後拋出,讓連接池自動更換 Physical Connection。NumUnavailable、ActiveConnectionsHighCount 指標,異常激增時告警。