2014-07-17 127 views
1

我基本上構建了一個Java應用程序來處理和響應RPC事件。我發現自己一直在做下面的事情,而我的Java知識正在打磚牆。Java數據庫交互模式

 PreparedStatement preparedStatement = null; 

     try { 
      preparedStatement = conn.prepareStatement(removeFollowersStmt); 
      preparedStatement.setLong(1, Long.parseLong(conversation)); 
      preparedStatement.setLong(2, Long.parseLong(userId)); 
      preparedStatement.executeUpdate(); 

      return true; 
     } catch (SQLException e) { 
      e.printStackTrace(); 
      return false; 
     } 
     finally 
     { 
      try { 
       assert preparedStatement != null; 
       preparedStatement.clearParameters(); 
       preparedStatement.close(); 
      } catch (SQLException e) { 
       e.printStackTrace(); 
      } 
     } 

理想的情況下,我只會做的try/catch /最後一個單一的時間,並能一些如何從嘗試中調用各種數據庫交互。

我不熟悉java來做到這一點,但我的想法可能是我可以創建一個函數,接受將在try中被調用的閉包?

+0

什麼是你的問題? – Sajmon

+0

我正在重複try/catch/finally在每個函數中,如果可能的話我只想做一次。 – Vinnyt

+0

,因爲它不是很好的關閉代碼中除finally代碼塊以外任何地方的連接,並且由於連接關閉會引發你想要處理的異常,所以除了2個try/catch塊之外別無選擇。 –

回答

3

看起來您已經發現,對於需要關閉的事件進行適當的異常處理(如JDBC Statement)是一種真正的痛苦。 Java 8 lambda可能可以在這裏提供幫助,但這是在Java 7中引入的try-with-resources statement的一本教科書示例。實際上,該教程有一個JDBC示例,但它與您嘗試執行的示例有點不同這裏。

在我們可以嘗試使用資源之前,我們需要仔細查看您的finally區塊。首先,斷言preparedStatement != null在這一點上實際上是不正確的。執行塊頂部的conn.prepareStatement語句在分配preparedStatement之前可能會拋出SQLException,所以在finally塊執行時它可能實際上仍爲空。 (大多數人在這裏添加一個空檢查,如果它是非空的話只關閉語句。)try-with-resources語句通過初始化之外的資源來避免這個問題,其finally-block負責關閉try-finally語句資源。

其次,斷言後有clearParameters的調用。我不認爲這是必要的。該聲明即將關閉,並且preparedStatement變量即將超出範圍,因此它將變得無法訪問,因此被垃圾收集。清除參數不應該有任何效果。

記住這些要點後,很明顯finally塊的主要責任是關閉語句,從關閉操作中處理任何SQLException,從而允許封閉方法正常返回。這幾乎是試用資源所做的。

重寫代碼使用try-與資源提供了以下:

try (PreparedStatement preparedStatement = conn.prepareStatement(removeFollowersStmt)) { 
     preparedStatement.setLong(1, Long.parseLong(conversation)); 
     preparedStatement.setLong(2, Long.parseLong(userId)); 
     preparedStatement.executeUpdate(); 
     return true; 
    } catch (SQLException e) { 
     e.printStackTrace(); 
     return false; 
    } 

這是正確的,整個finally塊可以被丟棄!不過,確切的行爲與您的原始代碼有所不同。差異在於如果通過executeUpdate成功完成所發生的事情,但關閉語句會拋出SQLException。在原始代碼中,將打印堆棧跟蹤,並且該方法將返回true

在修訂後的代碼中,來自close調用的例外會被此處的單個catch-clause捕獲,這將打印堆棧跟蹤並返回false。我不知道這是否正確。我的印象是,如果關閉語句引發異常,則可能意味着先前執行的更新實際上並不成功。如果是這樣,那麼返回false在這裏是正確的。 (但我不是JDBC專家。)

這比以前更好,但仍有try-catch樣板,您必須在每個語句執行期間添加try-catch樣板。你可以重複使用這個結構並傳入lambda?我認爲是這樣,但我們必須先做一些準備。該方法將需要一個用於創建PreparedStatement的SQL字符串和一個負責將參數設置到語句中的lambda。這裏的問題是PreparedStatement的安裝人員都可以投擲SQLException。在Java中8的java.util.function包處理這個內置的功能接口都沒有,所以我們要創造我們自己的功能接口:

interface StatementPreparer { 
    void prepare(PreparedStatement ps) throws SQLException; 
} 

現在我們有了這一點,讓我們寫的是準備一個語句並執行的方法它,處理異常,並返回一個布爾狀態:在要執行的實際工作的代碼

boolean update(String sql, StatementPreparer sp) { 
    try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { 
     sp.prepare(preparedStatement); 
     preparedStatement.executeUpdate(); 
     return true; 
    } catch (SQLException e) { 
     e.printStackTrace(); 
     return false; 
    } 
} 

現在,就可以發出一個電話是這樣的:

boolean result = update("delete from followers where conv = ? and userid = ?", 
    preparedStatement -> { 
     preparedStatement.setLong(1, Long.parseLong(conversation)); 
     preparedStatement.setLong(2, Long.parseLong(userId)); 
    }); 
+0

我正在與postgres數據庫進行交互,並且我正在嘗試維護一個連接。這可能是我做錯了別的地方,但我發現的是,如果我沒有調用clearParameters,那麼下次我會調用conn.prepareStatement(sql),即使在設置新參數時它會使用參數set第一次。我將試驗這兩種解決方案。我的java印章相當陳舊,所以感謝耐心的解釋。 – Vinnyt

+0

@Vinnyt Hm,這是奇怪的行爲,JDBC允許驅動程序有很多奇怪的行爲。如果這仍然是一個問題,我們可以在嵌套的try語句中使用try-block的前兩行(對'sp.prepare'和executeUpdate的調用),並使用一個清除參數的finally子句。有點醜,但至少你只需要做一次。 –

1

我猜這是一個Web應用程序,因爲「追隨者」這個詞。以下內容也適用於獨立應用程序,但會有所不同。

有你的代碼,這使得您的生活更難比它需要的是兩個概念上的問題:

1.您正試圖處理在錯誤的地方例外。

如果你發現自己寫的代碼,如:

try { 
    doSomething() 
} catch(SomeException e){ 
    e.printStackTrace() 
} 

那麼什麼是錯的與你的程序的「佈局」。如果將異常處理移動到調用鏈的某個位置,您會更好。

萬一這是一個web應用程序在servlet中執行它。所以你的情況,這將是:

void executeMyStatements throws SQLException { 
    try (PreparedStatement preparedStatement = conn.prepareStatement(removeFollowersStmt)) 
     preparedStatement.setLong(1, Long.parseLong(conversation)); 
     preparedStatement.setLong(2, Long.parseLong(userId)); 
     preparedStatement.executeUpdate(); 
    } 
    return true; 
} 

MyServlet extends HttpServlet { 

    void doGet(...){ 

     try { 
      executeMyStatements() 
      doSomeMoreStuff() 
      executeMyOtherStatements() 

     } catch(Throwable t){ 

      doSomethingMeaningfullWithException(t); 
      // e.g. t.printStackTrace(response.getOutputStream()); 
      // logger.error(t); ... 
     } 
    } 
} 

2.使用交易

和地方(也許你反正這樣做,但萬一沒有)

交易將幫助你。如果上一次失敗,你確定是否可以繼續進行下一個陳述?還是你的數據庫處於一種狀態,你最好從你想做的事情開始?

這很適合在別處處理異常的方法。基本上你想要做的是什麼:(

try{ 
    Connection con = createConnectionSomehow(); 
    Transaction transaction = con.startTransactionSomehow(); 

    executeSomeStatements(); 
    doStuff() 
    doDeeperStuffWithMoreStatementsWhichCallOtherStatementsDeepBelowInMoria(); 

} catch(Exception t){ 

    transaction.rollback(); 
    doSomethingUsefullWith(t); 

} finally { 

    transactions.commit(); 
    connection.close() 
} 

您必須包裹在finally一些語句與if != null我爲了更清楚地顯示主要概念省略這不應該要努力。

哦,順便說一下:您正在使用parseLong創建您的語句。這將失敗某天或另一個NumberFormatException這是一個RuntimeException不需要被捕獲。在你的方法中,你只能處理SQLException,因此另一個會傳播,並且(在最壞的情況下,在一個獨立的應用程序中)會使應用程序崩潰。如果您使用我的方法,則此異常會被catch(Throwable t)捕獲,並且將得到妥善處理。

以建議的方式組織您的代碼將使您的生活變得更加輕鬆,並避免代碼重複,而您在改進一般代碼質量和穩定性時不喜歡這些代碼。它會給你更好的錯誤處理,以防出現問題。