異常與數據庫連接死鎖

        在我所接觸的軟件開發人員中,已經聽到N多次數據庫死鎖的問題,說實在的,每次聽到有人向他們報告數據庫死鎖的問題,我總是感到很詫異,始終懷疑他們的代碼有問題。如果各種異常能得到合理的處理,會將死鎖的可能性降到最低。
        怎樣合理地處理JDBC編程中的異常問題,好多權威的書籍也沒有給出滿意的答案。在2003年電子工業出版社出版的美國計算機寶典叢書《Java數據庫編程寶典》一書中,在主要的章節,關閉數據庫連接均在catch代碼塊中進行,直到最後的第24章多次出現令人質疑的同樣的一段代碼

        finally {
            if (con != null) {
                con.close();
            }
            if (stmt != null) {
                stmt.close();
            }
        }
        在這段代碼中,關閉資源時,出現了次序錯誤

        在《Java2核心技術》(第7版)下卷第四章數據庫編程一章中,找不到有關數據庫事務編程的異常處理的代碼。由此可見Java界缺乏對JDBC的應用開發進行深入的研究。


        而有關多個異常的處理的代碼絕大多數是按照下面的結構實現

        try {
        } catch (SQLException e) {
        } catch (IOException e) {
        } catch (XXXException e) {
        } finally {
        }


        如果沒有打開數據庫連接事務,代碼可能是這樣的:

        try {
            ...
        } catch (SQLException e) {
        } catch (IOException e) {
        } catch (XXXException e) {
        } finally {
            con.close()
        }
        在這種情況下,數據庫發生死鎖的可能性爲零。


        如果打開了數據庫連接事務,代碼可能是這樣的:
        try {
            ...
            con.commit();
        } catch (SQLException e) {
            con.rollback();
        } catch (IOException e) {
        } catch (XXXException e) {
        } finally {
            con.close();
        }

        在這樣混合了多種異常的代碼中,如果發生了非SQLException異常,極有可能出現死鎖的情況,一旦發生非SQLException異常,事務既沒有提交,也沒有回滾,雖然JDBC關閉了數據庫連接,但是在數據庫服務器中,數據庫事務仍然是打開的。在使用SQL Server2000時,這一點可以通過對SQL Server2000的企業管理器/管理/當前活動/鎖/對象等部分觀察而得到結論。其它的數據庫產品是否也有類似的情況?這段代碼看似優雅,但缺乏健壯性;從代碼的健壯性考慮,應當將代碼拆分成三個try{}catch(XXXException e){}finally{}。如果希望維持代碼的優雅,則應當在這段代碼的實現細節上多下點功夫,比如多增加幾個boolean變量,看代碼能否執行到下一個階段。

        JadePool在處理異常上,還是下了相當的功夫的。在覈心類ProcessVO中,凡是與DML操作的相關異常,均進行了整合,統一拋出SQLException異常。比如,在實現插入的底層核心代碼中,共出現了三種類型的異常。
        try{
          ...
        } catch (java.lang.ClassCastException ex) {
            throw new SQLException("java.lang.ClassCastException: " + ex.getMessage(), ex.getCause());
        } catch (NumberFormatException ex) {
            throw new SQLException("NumberFormatException: " + ex.getMessage(), ex.getCause());
        } catch (IOException ex) {
            throw new SQLException("IOException: " + ex.getMessage(), ex.getCause());
        } finally {
            pstmt.close();
        }
        相關的資源打開和關閉,分散在整個ProcessVO的多個方法中,但在關閉資源時,嚴格注意次序。對於數據庫連接(或者叫會話)、事務、PreparedStatement對象、Statement對象、ResultSet對象等資源,ProcessVO中遵守的次序是:先打開,後關閉,後打開,先關閉



        ProcessVO中實現事務提交的代碼

    /**
     * 提交事務。
     *
     * @throws SQLException
     */
    public void commit() throws SQLException {
        if (!con.isClosed()) {
            if (!autoCommit) {
                try {
                    con.commit();
                    autoCommit = true;
                    con.setAutoCommit(autoCommit);
                    failCommit = false;
                    con.close();
                } finally {
                    if (failCommit) {
                        rollback();
                    }
                }
            }
        }
    }


        ProcessVO中實現事務回滾的代碼

    /**
     * 事務回滾。
     *
     * @throws SQLException
     */
    public void rollback() throws SQLException {
        if (!con.isClosed()) {
            if (!this.autoCommit) {
                if (failCommit) {
                    if (savepoint == null) {
                        con.rollback();
                        con.close();
                        System.out.println("[" + dt.dateTime() + "] SQLServerException. Connection is already rollback and closed .");
                    } else {
                        con.rollback(savepoint);
                        con.close();
                        System.out.println("[" + dt.dateTime() + "] SQLServerException. Connection is already rollback at " + savepoint + " point and closed .");
                    }
                    autoCommit = true;
                }
            }
        }
    }

        ProcessVO中關閉數據庫連接或者叫會話的的代碼

    /**
     * 關閉數據庫聯接
     *
     * @throws SQLException
     */
    @Override
    public void closeCon() throws SQLException {
        try {
            /*
             * if (rs != null && !rs.isClosed()) { rs.close(); }
             *
             * if (stmt != null && !stmt.isClosed()) { stmt.close(); }
             */
        } finally {
            if (con != null && !con.isClosed()) {
                con.close();
            }
        }
    }



        在ProcessVO中進一步完善了終結守衛者模式中的代碼

     /**
     * 終結守衛者,最後一道安全屏障,關閉聯接。開發人員不應依賴此方法來關閉聯接,但以下情況發生後,可能造成數據庫連接不能關閉,因此有必要保留終結守衛者模式。長期的實踐證明,終結守衛者能起到良好的作用。<br/>
     * 1、網絡連接終止;<br/> 2、應用程序中發生非SQLException異常後,可能導致數據庫連接沒有關閉。<br/>
     */
    private final Object _finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws SQLException, Throwable {
            if (con != null && !con.isClosed()) {
                try {
                    super.finalize();
                } finally {
                    if (!autoCommit) {
                        if (failCommit) {
                            try {
                                if (savepoint == null) {
                                    con.rollback();
                                } else {
                                    con.rollback(savepoint);
                                }
                                System.out.println("[" + dt.dateTime() + "] Connection rollback in finalize.");
                            } catch (SQLException ex) {
                                Logger.getLogger(ProcessVO.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                            } finally {
                                con.close();
                                autoCommit = true;
                                System.out.println("[" + dt.dateTime() + "] Connection is not autocommit, rollback and then closed in finalize.");
                            }
                        } else {
                            con.close();
                            System.out.println("[" + dt.dateTime() + "] Connection is autocommit, closed in finalize,You should close it.");
                        }
                    } else {
                        con.close();
                        System.out.println("[" + dt.dateTime() + "] Connection closed in finalize,You should close it.");
                    }
                }
            }
        }
    };


        以上這些代碼,能共同保證在發生SQLException的前提下,如果打開了事務,確保事務回滾,在關閉會話前關閉事務。
        在JadePool發佈前,有關異常與事務的提交、回滾之間的關聯做了大量的測試,能確保JadePool的核心類ProcessVO是健壯的、可靠的。
        在此,歡迎提出寶貴意見,幫助我更好的完善JadePool開源項目;更希望有實力的Java工作者參與。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章