[英]How to avoid stalled DB connections with TomEE's JDBC connection pool?
[英]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! 耶!
您希望“例外”做很多事情:
throw new RuntimeException("uncaught", e)
- 你應該更新你的 IDE 模板,這是正確的默認 catch 塊內容),但更好的是讓 SQLException 發生. 只是讓所有東西都拋出 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,您正在尋找的工具是JOOQ或JDBI 。 這些意味着你的復雜:
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.