首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

WebLogic PreparedStatement Cache 與 DDL 操作衝突問題深度解析與解決方案

     編輯:Java 相關知識

在 WebLogic 中啟用 PreparedStatement Cache 後,應用若執行 DDL (如 ALTER TABLE),後續復用缓存的 PreparedStatement 會導致 Oracle 拋出 SQLException: 違反協議 (Protocol Violation)。本文深度剖析根因、驗證代碼、WebLogic 官方文檔解讀,並給出生產環境可落地的兩種解決方案。

問題現象

  • WebLogic 數據源配置 Prepared Statement Cache Size > 0
  • 應用執行 DDL (如 ALTER TABLE test ADD (col VARCHAR2(10)))
  • 後續執行缓存的 PreparedStatement (如 SELECT * FROM test) 拋出:
    java.sql.SQLException: XA error: XAER_PROTO: Protocol violation 或 Unexpected exception while enlisting XAConnection

根因分析:Physical Connection 層面的狀態失效

核心結論:DDL 導致數據庫端游標/會話狀態變更,WebLogic 缓存的 PreparedStatement 綁定在 Physical Connection 上,而非邏輯連接。單純清理 Statement Cache **無效**,必須讓 Physical Connection 失效並重建。

為什麼清理 Statement Cache 不夠?

  • WebLogic 在 Statement 異常時會自動清理該連接上的 Statement Cache。
  • 但重新創建 PreparedStatement 時,仍復用同一 Physical Connection (池中對象)。
  • Oracle 端該 Physical Connection 的會話狀態已被 DDL 污染,新 Statement 繼承污染狀態 → 協議違規。

驗證代碼:繁現 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 官方解決方案:Remove Infected Connections Enabled

WebLogic JDBC 文檔 (thirdparty.html) 明確指導:

  1. 捕獲 SQLException 後,獲取 Vendor Connection (Physical Connection):
    java.sql.Connection vendorConn = ((WLConnection)conn).getVendorConnection();
  2. 不要手動關閉 vendorConn,交由連接池管理。
  3. 在數據源配置中啟用 Remove Infected Connections Enabled = true。

配置項含義

  • true:應用關閉邏輯連接時,**不將 Physical Connection 歸池**,而是關閉並重建新 Physical Connection 補充池。適用於調用 getVendorConnection() 後的場景。
  • false (默認):關閉邏輯連接時,Physical Connection 歸池復用。若已調用 getVendorConnection() 可能導致池狀態不一致。
啟用此項會影響性能 (禁用連接池復用),但能徹底解決 DDL 後協議違規問題。

完整處理流程代碼 (WebLogic 環境)

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,創建新的
}

替代方案:等待 Oracle 內部超時自動恢復 (約 1 分鐘)

  • Oracle 默認 60 秒檢測游標失效,~120 秒會自動清理會話狀態。
  • WebLogic 可配置 XA Transaction Timeout 大於全局事務超時,主動通知 RM 超時時間。
  • 不推薦生產環境依賴等待,業務不可用時間不可控。

關鍵限制與注意事項

  • getVendorConnection() 返回對象 **不可序列化** (Oracle T4CConnection),**僅能在 Server 端 (同 JVM) 使用**,不能傳遞到遠程客戶端。
  • 文檔建議**不要手動關閉 Vendor Connection**,交由連接池 Remove Infected Connections 機制處理。
  • 測試中手動關閉 vendorConn.close() 雖未報錯 (Oracle 驅動忽略重複關閉),但違反最佳實踐。
  • 此行為屬於 WAD (Work As Designed),非 WebLogic Bug。WebLogic 8.1 SP6 已有錯誤提示。
  • 其他數據庫 (DB2, SQL Server) 行為可能不同,需實測驗證。

總結建議

  1. **方案一 (推薦)**:數據源啟用 Remove Infected Connections Enabled = true,捕獲異常後拋出,讓連接池自動更換 Physical Connection。
  2. **方案二 (不推薦)**:業務層面避免在使用 PreparedStatement Cache 的連接上執行 DDL,或 DDL 執行後主動刷新連接池 (重啟數據源)。
  3. 監控連接池 NumUnavailable、ActiveConnectionsHighCount 指標,異常激增時告警。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved