什麼是光池?
BlueSky 上的一篇文章中的這個簡單問題讓我得到了一個我認為非常酷的解釋。我來這裡是為了完成它。
在具體的上下文中,正在討論 Hikari 連接池。但是,如果 Hikari 是一個連接池,那麼「池」會是什麼?
在解釋什麼是 HikariCP 之前,我們需要先解釋一下連接池是什麼。為了解釋連接池,我們需要解釋池。
我們可以用經濟學來類比嗎?這是一個與現實世界充滿缺陷和不準確的歷史經濟類比,但是來吧,為了解釋而迅速停止你的懷疑!它是獨立的。
想像你是中世紀時代的領主/女士。你拿著工具來進行農民的工作。並且您希望它們能夠工作。那麼如何保證這一點呢?如果工具是你的?你需要提供農民工具,很簡單。
所以想像一下這種情況:你的農民需要一把鋤頭來除草,所以他去那裡向你要一把鋤頭。你會給他鋤頭並繼續生活。但如果他不歸還,他的鋤頭怎麼辦?總有一天會結束的…
交出鋤頭的另一個選擇是製作鋤頭。您是這些土地的領主/女士,因此您可以聯繫鐵匠,將金屬熔煉成鋤頭的形狀並將其安裝到手柄中。但這不是你可以在沒有農民坐在候診室的情況下立即生產的東西。為了實現這個新功能,您需要大量的時間和精力。
現在,如果農民在一天結束時歸還鋤頭,第二天它就可以供其他農民使用。
在這裡,您正在控制鋤頭池。 池是一種設計模式,指示您可以執行以下操作:
物件池中的其他常見內容:
那麼,讓我們離HikariCP更近一些吧。這裡我們來談談Java中的資料庫連線。
在java中,我們要求建立與資料庫的連線。有直接連接選項,您需要直接了解要呼叫哪些類別以及一些細節,或者只是享受服務發現選項。
先驗地,為了使用服務發現,服務提供者會提供一種方法來註冊他所提供的內容,然後「服務發現」會追蹤它以查看誰可以服務該請求。
我曾經遇到過需要建立 JDBC 連線來與資料庫通訊的情況。但是,我的 JDBC 驅動程式不接受使用 null 作為值,只接受直接在查詢中使用 null。那我做了什麼?司機頂司機!
整體思路如下。想像一下,我有一個查詢,我想將值插入到:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
現在想像一下我正在處理這個值第一次插入銀行。為此,我需要將其保留為 ID=1、CONTENT=first 和 PARENT=null,因為畢竟沒有這樣的父親記錄(畢竟它是第一個)。
自然會做什麼:
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
我想繼續這樣使用它,畢竟這是慣用的使用方式。根據丘比特的說法,「I」來自「慣用語」。擁有慣用代碼的想法正是為了「減少不必要的心理負擔」。
為了解決這個問題,我的選擇是:將prepareStatement留到executeUpdate之前的最後一刻。因此,我儲存了要應用的所有空值,當我意識到我實際上需要一個空值時,我運行字串替換並產生一個新查詢,並且這個新查詢將實際執行。
在這種情況下,我從:
開始
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
所以,我必須輸入這些值:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
但我實際上無法使用 null,所以我創建了一個鍵來標識第三位是 null:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
在這種情況下,我準備這個新字串並根據要求放置參數。
好吧,也就是說,我如何向我的應用程式表明我需要使用 JDBC 驅動程式?我如何註冊?
相關項目是 Pstmt Null Safe。基本上,Java 類別載入器有一個神奇之處,就是在載入 jar 時,它會尋找名為 META-INF 的元資料資料夾。對於 JDBC 驅動程序,META-INF/services/java.sql.Driver,我用實作 java.sql.Driver 的類別記錄了它:br.com.softsite.pstmtnullsafe.jdbc.PstmtNullSafeDriver。
根據 java.sql.Driver 文檔,每個驅動程式都應該建立自己的實例並向 DriverManager 註冊。我是這樣實現的:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
靜態區塊自行載入。我們如何知道哪個連接應該由我的驅動程式管理?該呼叫是透過 DriverManager#getConnection(String url) 進行的。我們有 URL 來詢問驅動程式是否接受連線。約定(這裡又是慣用的使用方式)是將其作為 URL 方案的前綴。因為我希望我的驅動程式連接到另一個驅動程式之上,所以我使用了以下方案:
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
因此,為了執行測試,我連接了 SQLite,並使用 Xerial 指示器透過連接 URI 請求記憶體中連接:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
為了「封裝」連接,我的約定表明我不重複 jdbc:,所以:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
剖析上面的 URI:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
好的,你如何表明這一點?如果我可以打開連接,Driver#acceptsURL 必須傳回 true。我可以這樣做:
public static final PstmtNullSafeDriver instance; static { instance = new PstmtNullSafeDriver(); try { DriverManager.registerDriver(instance); } catch (SQLException e) { e.printStackTrace(); } }
但是如果我嘗試加載不存在的驅動程序,這表明什麼?沒什麼,下次會出問題的。這並不好,理想的情況是從一開始就崩潰。因此,為此,我將嘗試從下面加載驅動程序,如果不能,我將返回 false:
jdbc:pstmt-nullsafe:<url de conexão sem jdbc:> \__/ \____________/ | | | Nome do meu driver Padrão para indicar JDBC
實際的驅動程式程式碼還有一些與此處討論的 HikariCP、DataSource、JDBC 或本文討論的主題無關的要點。
因此,當請求與 DriverManager 的“空安全”連接時,它首先找到我的驅動程序,然後我的驅動程式遞歸地嘗試檢查是否有可能在後台建立連接。確認有一個驅動程式能夠處理這個問題,我說是的,這是可能的。
Connection 介面實作了 AutoCloseable 介面。這表示您獲取連接,根據需要使用該連接,然後關閉該連接。對此使用一些間接連接是非常標準的,或者,如果您直接使用連接,請在 try-with-resources:
區塊中使用它
jdbc:sqlite::memory:
現在,創建連接的過程是一個昂貴的過程。而且服務發現過程並不是完全免費的。因此,理想的情況是保存驅動程式,然後產生連接。讓我們一點一點地開發它。
首先,我們需要有一個可以透過驅動程式啟動的物件。它可以很容易地是一個全域物件、一個注入的 Spring 元件或類似的東西。我們稱之為 JdbcConnector:
jdbc:pstmt-nullsafe:sqlite::memory:
getJdbcConnection() 的一種可能實作是依賴該函數包含的狀態:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
到目前為止一切都很順利。但是……還記得最初的例子嗎?農民在工具池中索取一把鋤頭?那麼……我們要考慮到這一點嗎?我們可以將連線返回池,而不是實際關閉連線。為了正確性,我會防止多個同時訪問,但我不會擔心這裡的效率。
我們假設我有一個名為 ConnectionDelegator 的類別。它實作了所有 Connection 方法,但它自己不執行任何操作,它僅委託給作為建構函式傳遞給它的連接。例如,對於 isClosed():
方法
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
其他方法依此類推。它是抽象的,因為一個簡單的事實是,當我使用它時,我想強迫自己做除了簡單委託之外的事情。
好吧,我們走吧。這個想法是,將請求連接,該連接可能存在或不存在。如果存在,我將其包裝在這個新類別中,以便在關閉連接時將其返回到 pool。嗯,所以我要在 close() 方法中做一些事情...好吧,我們先包裝一下。讓我們將 getConnection() 保留為同步以避免並發問題:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
好的,如果我的連接池中有元素,我會使用它們直到它為空。但它永遠不會被填滿!那我們要解決這個問題嗎?當它關閉時,我們可以將其返回池!
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
好的,現在當您使用完連接後,它會被發送回
泳池。這不符合 Connection#close() 方法的文檔,因為在文檔中它提到它會釋放與此連線相關的所有 JDBC 資源。這意味著我需要保留所有語句、結果集、PreparedStatements 等的記錄。我們可以透過在 ConnectionDelegator 上建立一個名為 closeAllInnerResources() 的受保護方法來處理此問題。並在 close() 中呼叫它:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
這樣我們就可以根據需要向我返回連接,並且能夠形成資源池。
你知道Java為提供連線的物件取什麼名字嗎?資料來源。您知道 Java 對 DataSource 還說了什麼嗎?從概念上講,有一些類型。在這些類型中,最相關的 2 種是:
這裡我們經歷了始終創建連接(基本類型)以及演變為資料來源的過程
合併。
HikariCP 是一個資料來源。具體來說,就是一個池化資料來源。但他有一個特點:他是所有人中速度最快的。為了確保這個速度,在應用程式生命週期中使用的連接池中,HikariCP 制定了一個秘密:它已經創建了所有可用的連接。因此,當 getConnection 到達時,HikariCP 只需要檢查連接池。
如果你想更深入地研究這個主題,你可以查看 Baeldung 上有關該主題的這篇文章,也可以查看 github 上的存儲庫。
以上是什麼是光池?的詳細內容。更多資訊請關注PHP中文網其他相關文章!