簡體   English   中英

如何避免 jdbc postgres 的多個連接

[英]How to avoid multiple connections for jdbc postgres

我需要根據之前的查詢結果查詢一個postgresql database 現在我為每個查詢都有一個新的DriverManager連接,這對我來說似乎不是最佳實踐(我是 Java 新手)。

public static Integer item(DataModel params) {

    Psqlcon psql = new Psqlcon();
    
    try (Connection con = DriverManager.getConnection(psql.url, psql.user, psql.password);
         PreparedStatement pst = con.prepareStatement("SELECT * FROM table1 WHERE var=? "
        pst.setObject(1, params.getID());
       
        ResultSet find_id = pst.executeQuery();{

            while (find_id.next()) {

                id = find_id.getInt(1);
            }
            pst.close();
        }

    } catch (SQLException ex) {

        Logger lgr = Logger.getLogger(something.class.getName());
        lgr.log(Level.SEVERE, ex.getMessage(), ex);
    }

    try (Connection con = DriverManager.getConnection(psql.url, psql.user, psql.password);
             PreparedStatement pst = con.prepareStatement("SELECT something FROM table2 WHERE id=? "
            
        pst.setObject(1, id);
       
        ResultSet find_next = pst.executeQuery();{

            while (find_next.next()) {

                id2 = find_next.getInt(1);
            }
            pst.close();
        }
   
        ....on so on

這是一個過於簡單的例子。 查詢實際上要復雜一些。 有一個connection並只關閉prepareStatement不是更好嗎? 如何實現只有一個連接?

您的問題歸結為 state 跟蹤。 整個項目中的大量代碼都需要使用 DB,而 DB基本上是面向 session 的概念。 因此,您將需要更改您的代碼流——任何需要對 DB 執行任何操作的代碼不能只是 go:“嗯,好吧,我將 go 建立連接”。 它需要獲得提供給它的現有連接,因為“每次都建立新連接”是行不通的。 不僅因為這很慢,而且主要是因為交易是一回事。

這就是注入框架的用武之地:它們都是為了讓 state 存在於項目的大量位置。 但是,您也可以“手動”完成,可以這么說:傳遞一個Connection object 作為方法參數。

這段代碼中有一些代碼異味。 這些問題正在引導您提出這個問題。

異常處理

“發生異常時,將一些內容打印到日志並繼續運行”是您的代碼現在所做的。 鑒於我是這樣說的,希望現在很明顯,這是一個非常糟糕的主意。 應該聲明一個從根本上做數據庫事情的方法throws SQLException 整個“代碼流”(最終被調用以執行特定作業的每個方法)都需要是ALL聲明為throws SQLException的方法,此時您可以擺脫所有 try/catch 塊。 在使代碼變得更好的同時,您將大大減少代碼 LOC! 耶!

您希望“例外”做很多事情:

  • 停止執行。 當您在您不理解的 state 中時,您不希望代碼繼續運行。 SQLException 有很多原因,因此,您不知道發生了什么。 繼續下去是不好的。 一個簡單的解決方法是讓 catch 塊重新拋出一些東西( throw new RuntimeException("uncaught", e) - 你應該更新你的 IDE 模板,這是正確的默認 catch 塊內容),但更好的是讓 SQLException 發生.
  • 您希望盡可能多地保存相關信息:堆棧跟蹤、因果鏈、確切的異常類型、消息以及異常 object 具有的任何特殊信息 - SQLException 有很多這樣的信息,例如 postgres 錯誤 state非常重要的數字,並不總是包含在消息中。

只是讓所有東西都拋出 SQLException 就可以完成所有這些 - 很少有地方(所有頂級調用)可以編寫處理它的代碼,而不是編寫 83951 次lgr.log(Level.SEVERE)

每次都會創建連接

您不想這樣做 - 您希望傳遞連接。 通常,您需要更強大的設置來跟蹤交易。 所有數據庫的一切都始終處於事務形式。 即使你有 autocommit true (這就是為什么它被稱為'AutoCommit'而不是'transactionless'):這只是意味着數據庫假設你打算在每次查詢后執行db.commit()

甚至讀取查詢也是事務性的。 想象一家銀行需要為合伙企業的收購提供銀行擔保。 為此,它需要總結合伙企業中每個成員的余額。 你可以簡單地做:

int sum = 0;
for (Partner p : partners) {
  try (var pst = con.prepareStatement("SELECT balance FROM table2 WHERE id=?")) {
    ... 
    sum += rs.getInteger(1);
  }
}

return sum > requiredGuarantee;

但是,上面的代碼被巧妙地破壞了 想象一下,就在這段代碼的中間,一個合伙人將一大筆錢轉移給了另一個合伙人。 您碰巧在轉賬之前讀取了將現金轉出的合作伙伴,但在其之后讀取了接收合作伙伴的余額。 您的代碼現在有錯誤的總和。 事情是所有合伙人賬戶中的總金額遠高於實際金額,銀行提供擔保,交易失敗。 銀行剛剛損失了1000萬美元。 哎呀,真是個bug? 不是嗎?

解決方案是交易。 以及正確的事務級別。 在正確的級別(SERIALIZABLE 是唯一可以正確執行此操作的級別),如果您在單個事務中執行所有這些 SELECT,則不會發生上述情況。 Postgres 會讓你像往常一樣查詢和總結,你可能確實得到了錯誤的總和,但是.commit()調用(請注意,在這個事務中我們還沒有寫任何東西!)會失敗 這是你重新開始的提示。

連接與事務處於同一級別:如果要同時運行 2 個事務,則必須有 2 個連接。 單個連接在事務中。 因此,傳遞連接 object傳遞事務相同,並且您必須具有如上面代碼所示的事務(想象銀行擔保最終由在“擔保”表中插入新行來表示。你想要這一切都在一個事務中,所以畢竟,如果由於在兩者之間發生了一些其他事務而求和不一致,那么你.commit()和 postgres 會拋出一個異常(你寫的那一行從未發生過)這改變了一個或多個合作伙伴的平衡。

這是寫入“一致”數據 model 的唯一正確方法。

對於某些項目,一致性成本太高。 然而,除非你的名字是真正的 google,否則你永遠不會到達那里,如果你必須擁有這個,它比僅僅選擇較低的事務隔離級別要復雜得多。 所以,為了一致性,go。

如果您希望Connection con成為所有基於 db 的方法的第一個參數,或者您想創建一個 object 並擁有一個類型為“con”的字段,這取決於您(但請記住,它們必須被關閉,所以創建此類 object 的代碼必須使用 try-with-resources 來關閉該連接,或者您的 class 具有字段Connection con需要為AutoClosable ,並且必須使用 try-with-resources 創建。) - 或如果你想使用像 spring 或 dagger 或 guice 這樣的注入框架。

使用抽象

JDBC 主要設計用於公開每個關系數據庫系統可能具有的每個功能,並成為一致的“底層”。 這意味着作為一個 API 直接在其上編寫應用程序,這太可怕了 它不適合你。

使用為此目的設計的庫。 它將建立JDBC 之上。 您正在有效地編寫直接機器代碼。 這很少是一個好主意。

您可以使用 JPA(對象持久性框架),如 Hibernate,但這是一個全新的蠕蟲罐。 如果您喜歡 SQL,您正在尋找的工具是JOOQJDBI 這些意味着你的復雜:

pst.setObject(1, id);
       
        ResultSet find_next = pst.executeQuery();{

            while (find_next.next()) {

                id2 = find_next.getInt(1);
            }
            pst.close();

都變成了簡單的單行。

連接池

創建連接 object 真的很昂貴。 如果您想要多個同時交易,這是您必須支付的不可避免的成本,但如果您有多個連續交易,您真的不想支付它。 這在一定程度上取決於 DB 引擎和 JDBC 驅動程序(有些甚至有內置的池系統,有些則沒有)。 但一個簡單的解決方案是添加一個抽象層:您可以請求連接池,而不是要求 DriverManager 為您獲取連接。 它為您提供了一個虛擬化連接 object ,它通過所有調用,除了close() ,它實際上並沒有關閉連接,而只是使虛擬 object 無效並將實際連接傳遞回“池”。

在您獲得大量流量之前,這不是必需的,但是如果您想做好充分的准備,那么您可以看看它。 HikariCP這樣的庫使這變得容易。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM