(14) 深入 JDBC、Connection Pool,並導入 H2 DB

Albert Hg
learning-from-jhipster
18 min readNov 13, 2020

這個系列文從這一篇開始,將會進入連接資料庫的部分。在上一篇文章中我們有稍微簡單的介紹了 JDBC,但其實 JDBC 的內容還有很多是我們在上一篇中沒有提到的。

因此本篇文章一開始會先更詳細的介紹 JDBC 與 Connection Pool,介紹完後才會開始將資料庫導入。

在導入資料庫的部分這裡會選用 H2 DB,作為我們開發用的資料庫,並詳細介紹在連接資料庫時所需的一些設定細節。

JDBC

若想要讓應用程式與資料庫進行連接與溝通,那麼 JDBC 則是不可或缺的 Library。它可以透過每個資料庫所對應的 JDBC Driver 來與資料庫進行連接,並透過 JDBC 的 API 來向所指定的資料庫發送想要執行的 SQL 命令。

其中,在連接的部分會必須建立連接的 URL,這個 URL 會因為不同的資料庫有不同的寫法,不過其實都大同小異,這裡提供幾個比較常見的資料庫的 URL 設定方式:

https://gist.github.com/albert-hg/feba73361cd66e567d6be3925528625b

每次進行連接所需要的成本卻是非常昂貴 (Expensive) 的。所謂「昂貴」就是說這些動作是「非常耗費系統資源」的行為。如果直接透過 JDBC 的 API 與資料庫進行連線,像是如下片段程式碼:

https://gist.github.com/albert-hg/2e2c0d2ca9b658cfe3faca58a4126042

那麼一次連線的週期就會產生如下動作:

  1. 載入對應的 JDBC Loader
  2. 建立連線 (需要呼叫 DriverManager.getConnection()),開啟 TCP socket
  3. 建立 Statement ,並提交 SQL 命令列
  4. 關閉連線,關閉 TCP socket

然而每一次需要與資料庫進行溝通時,都要這樣開開關關,對於系統來說就會造成許多不必要的負擔。如果我們可以於每次連線時,將那些一直重複的動作提取出來,那麼就可以減少對系統的負擔,並且加快程式的執行效能。

這時候我們就可以使用「Object Pool Pattern」的設計模式。

Connection Pool

根據 維基百科對於 Object Pool Pattern 的定義如下:

一個物件池包含一組已經初始化過且可以使用的物件,而可以在有需求時創建和銷毀物件。

池的用戶可以從池子中取得對象,對其進行操作處理,並在不需要時歸還給池子而非直接銷毀它。這是一種特殊的工廠物件。

若初始化、實例化的代價高,且有需求需要經常實例化,但每次實例化的數量較少的情況下,使用對象池可以獲得顯著的效能提升。

所以我們可以針對 JDBC 在建立連線與關閉連線的部分,使用這樣的設計模式來減少對系統的負擔以及增加效能。

一個簡單的 JDBC Connection Pool 可以參考下方連結,你可以在裡面看到一個 Connection Pool 是怎麼實做的:

但實際上他的基本原理就是在應用程式一啟動的時候,先建立好固定數量的 Connection 物件存放在 Pool 內,並透過 getConnection() 的方法從 Pool 裡面取得 Connection。等到跟資料庫取完資料後,透過 returnConnection() 的方法將用完的 Connection 返還,並存回 Pool 中。

透過這樣的模式,就可以避免頻繁建立、釋放 Connection 引起大量的效能浪費,並且因為減少了記憶體碎片產生等問題進而增加了系統執行的平穩性。

現在我們可以理解 Connection Pool 的運作原理了,那我們有需要自己刻一個來使用嗎?當然可以(以下省略一萬八千個字XD),但其實已經有許多的第三方套件供我們使用,而且這些第三方的 Connection Pool 套件也已經非常成熟,在效能上以及減少重工的基礎上,直接使用第三方套件會是更好的選擇。

如果你想更深入了解這些 Connection Pool 的實現細節,你可以參考 資料庫連線池的實現及原理 這篇文章。

會發展出不同的第三方的 Pool 主要原因就是因為效能,因為 JDBC 針對JDBC Driver 的介面有做了規範,因此各家廠商做出來的 Connection Pool 也都會符合 JDBC Driver Interface 的 API,也就是 API 都相同,差別就在於不同第三方套件的內部實做所影響的效能。

HikariCP

目前最厲害、效能最好的第三方 Connection Pool 一定是非HikariCP莫屬了。Hikari,在日文中代表「光」的意思,意思就是說這個 Connection Pool 的執行效能跟速率,可以跑的跟光速一樣快一樣猛 (真的嗎?),我們來看看下方的對照圖:

https://raw.githubusercontent.com/wiki/brettwooldridge/HikariCP/HikariCP-bench-2.6.0.png

在這張圖中,一個 Connection Cycle 代表 DataSource.getConnection()Connection.close() 結束。可以看到在這個 Cycle 的比較中,在一毫秒內 Hikari 可以完成 47609 到 50273 個 Cycle,至於其他...就被甩在後面了XD。

而一個完整的 Statement Cycle 則是從 Connection.prepareStatement()Statement.execute()Statement.close() 。可以看到在這個 Cycle 的比較中,在一毫秒內 Hikari 可以完成 146667 個 Cycle,比較可以跟的上的大概就只有 Tomcat 的 JDBC Connection Pool ,其他的效能也就不值一提了。

但是為什麼 Hikari 可以這麼快!?因為礙於篇幅關係,所以這部分暫時不在這裡進行討論,但如果真的很想知道原因的話,可以參考這篇文章:

其他 Connection Pool

這裡就不為其他 Connection Pool 多做介紹了 (Q_Q),畢竟這麼多家廠商都在搞 CP,但 Interface 也全部都是依照 JDBC 的規範在走的,能比較的也只剩下效能而已了。

  1. vibur-dbcp
  2. tomcat
  3. dbcp2
  4. C3PO

Spring Boot 連接資料庫的相關設定

前面的段落介紹了 JDBC 與資料庫進行連線的方式,也說明了 Connection Pool 的重要性。

而在 Spring Boot 的部分,就是一個「約定大於配置」的設計理念,他幫我們整合了一系列最常使用的工具,當然對於 JDBC 或是 Connection Pool 或是 JPA 等等相關的內容也是一併的簡化配置作業。

因此現在只需要簡單的 Import Dependencies 並加上 Properties 的設定,就可以快速又方便的與資料庫進行連線。那麼應該要加入那些 Dependencies 或要進行哪些設定呢?現在就來開始介紹怎麼使用 Spring Boot 連接資料庫。

spring-boot-starter-jdbc

不論是 嵌入式資料庫 或是 一般常見的關聯式資料庫,只要是想要透過 Java 的應用程式與資料庫溝通,那就如同上述一般,一定會需要 JDBC。如果是使用 Spring Boot 進行開發,那麼你就可以直接 Import 對應的 Starter:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

如果你有仔細去看看這個 Starter 的內容,會發現他其實主要包含兩個Dependency:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>

其中一個是 spring-jdbc,是 Spring 針對 JDBC 封裝後的一個 Library。如果使用 spring-jdbc,那麼就必須在 Properties 的部分設定 JDBC 的連線資訊 以及 Connection Pool 的指定,才可以與資料庫進行連線。設定 Properties 的部分會在下方繼續提到。

也因為必須要包含 Connection Pool,所以 Spring Boot 官方將 HikariCP 做為預設的 Connection Pool 並包含在 spring-boot-starter-jdbc 之中。

spring-data-jdbc

在這裡我們會需要 spring-data-jdbc 來增強 spring-boot-starter-jdbc 的使用,簡單來說,如果「單獨」只有 spring-boot-starter-jdbc 的話,除非加上許多額外的配置,否則是沒有辦法直接與資料庫進行連線的。

因此若加上 spring-data-jdbc 的話,那麼就可以少掉許多設定,簡單的使你的應用程式連上資料庫並與資料庫進行溝通。

更多細節說明:https://spring.io/projects/spring-data-jdbc
其他範例:https://www.baeldung.com/spring-jdbc-jdbctemplate

Properties 的設定「spring.datasource」

Spring Boot- Data properties 裡,有許多設定值可以參考,但實在有太多項目了,所以無法詳細介紹完整的內容。在這裡只會挑出幾個比較常用的、重要的重點來說明,並將 properties 的格式改為使用 yml 的格式撰寫,且加上了一些說明:

https://gist.github.com/albert-hg/94c2fe6b07866bcc33b93dbe085a0ec8

其中的 urlusernamepassword 是不可或缺的設定內容。而 type 則是不一定要進行設定,若依照官方的說明:

Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.

代表 spring.datasource.type 的值,因為導入了 JDBC Starter,所以 Spring Boot 會從 ClassPath 中自動偵測有哪些 Connection Pool 可以被使用。如果你想要知道知道他是如何自動偵測的,或是可以自動偵測哪些 class name,可以參考 DataSourceBuilder.java — Source Code,或參考下方程式碼:

https://gist.github.com/albert-hg/a602c00b1c4ef19f6990124f7484f7aa

在這個片段程式碼中,我們可以看到 Spring Boot 會自動偵測如下幾個 Connection Pool:

另外關於 driver-class-name 的部分,依照官方的說明:

Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.

則代表 Spring Boot 會從所設定的 url 中自動檢測要使用哪一種 Driver,如果你想要知道是如何自動偵測,或是可以自動偵測哪些 Driver,可以參考 DataSourceBuilder.java - Source Code 以及 DatabaseDriver.java - Source Code,或參考下方程式碼:

https://gist.github.com/albert-hg/e24520e72646c3655a06c19219452f71

H2 Database

H2 資料庫,是一個以 Java 開發的開源資料庫,他可以做為「嵌入式的資料庫」也可以做為「伺服器模式的資料庫」。正因為他的嵌入式的特性,使 H2 能簡單的又方便的導入,因此在開發 Java 的應用程式時,我們就會使用 H2 做為開發的資料庫。此外,他也有許多優點,例如:速度快、體積小、支援 JDBC、可以直接在瀏覽器中看到 Console 的頁面等優點。

當 H2 做為嵌入式資料庫時,可以有兩種不同的形式:

  • 「Memory-Base」:資料以存放在記憶體中,因此當應用程式關閉時,資料庫也會被清除。
  • 「Disk-Base」:資料以存放在硬碟中,因此當應用程式關閉時,資料會以 File 的方式存放在某個資料夾位置內,其資料夾位置可以被設定。

也因為資料容易重置的關係,因此除了在開發環境中常常使用到 H2,在測試環境中,我們也常常會拿 H2 來做為單元測試等相關應用。

導入 H2

在導入 H2 之前,必須先確保 spring-boot-starter-jdbc 已經有加入完成:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

接著再將 H2 Dependency 導入即可 (這裡你可以考慮是否要將 H2 DB 只容許在 Dev 環境中才可使用,如果是的話就要加入到對應的 <profile><dependencies> 中):

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

然後我們將專案執行,可以看到底下的 Log。圖中的第一個框,顯示著 Hikari Connection Pool 的建立資訊。而第二個框,顯示著 H2 的建立資訊,其中的 jdbc:h2:mem:00af05e8-b2ea-453d-9579-a5d78236c68e 是 JDBC 的連線 URL,而 mem 代表著預設是以 Memory-Base 的方式建立 H2資料庫。

接著我們打開瀏覽器,進入 http://localhost:8080/h2-console/login.jsp 就可以進入到 H2 的 Console 登入頁面。其預設使用者名稱為 sa,而密碼的部分預設為空值。

登入後的畫面如下,不過因為我們還沒開始將資料加入,所以裡面會是沒有任何資料的狀態:

H2 在 spring.datasource 的預設值

不曉得你會不會覺得明明都還沒有設定任何東西,那些所需參數是在哪裡已經被設定好了?

其實 Spring Boot 已經有針對常用的嵌入式的資料庫先進行預先配置了,可以參考 Spring Boot — EmbeddedDatabaseConnection.java — Source Code,或者參考下方幫你做好的整理:

https://gist.github.com/albert-hg/fdc5efb5c9644e39f85f7b931401d7ac

spring.datasource.url 的部分,是由 EmbeddedDatabaseConnection.java 產生的。

spring.datasource.username 以及 spring.datasource.password 的部分則是由 DataSourceProperties.java 所產生的。

又因為 url 的部分已經被預設為 jdbc:h2:mem:....... ,所以 Spring Boot 會自動識別 h2 的關鍵字,將 spring.datasource.driver-class-name 設定為 org.h2.Driver

H2 在 spring.datasource 的相關設定

因為 H2 的預設值是 Memory-Base ,這代表著如果應用程式被停止了,資料也會被全部清除。如果你想要讓你的資料持久化 (就是可以存起來),那麼就必須將 H2 改為 Disk-Base,也就必須修改設定值,例如:

https://gist.github.com/albert-hg/f5f5f6274ce6e77bf9da7842adc5fb9d

接著執行程式後就可以看到對應資料夾路徑底下會出現一個 local.mv.db 的檔案。

然後一樣到 http://localhost:8080/h2-console/login.jsp 進行登入,這裡要注意的是在 Console 登入頁面上, JDBC URL 、 User Name、Password 的值也必須要對應如所設定的值一樣,才可以順利進行登入。

一直還沒有提到在設定 H2 的 JDBC URL 後方有一個參數 DB_CLOSE_DELAY 的用處,這個參數對於 H2 DB 來說,是可以設定「當最後一個正在連接的 Connection 斷開的[幾秒後],自動關閉資料庫」的設定參數。當設定值為 -1 的時候,代表不要自動關閉。當設定值為 0 的時候,代表當最後一個連線斷開後,馬上關閉資料庫。而當設定值為 N 的時候,就代表當最後一個連線斷開的 N 秒後,馬上關閉資料庫。

H2 在 Spring Boot 的其他相關設定

一樣還是因為 Spring Boot 的「約定大於配置」,所以他也幫我們整理了關於 H2 的常用設定,並且允許可以直接修改 Properties 即可。至於有哪些參數可以進行設定呢?一樣是去參考 Spring Boot — Data Properties ,就可以找到相關的 H2 設定,但我一樣整理於下方供你參考:

https://gist.github.com/albert-hg/2cd999e2c039c89b0361e22850893927

文末

到這裡為止,我們已經完成將 H2 導入的動作了,只是裡面目前還沒有資料,所以在後續的文章內會繼續說明如何對資料庫進行資料表的新增、修改、刪除,並透過 JPA 來替資料表中的資料進行 CRUD 的動作。

只是在那之前,因為我們在這一篇是使用嵌入式資料庫的方式來展示怎麼使用 Spring Boot 提供的 JDBC Starter 來與資料庫進行連接,但我們還沒講到怎麼連到一般常見的像是 MySQL、SQL Server 或是 PostgreSQL。所以下一篇文章將以 PostgreSQL 為例,我們會找一個免費的線上資源建立資料庫,並嘗試將我們的專案連上遠端資料庫。

其他外部參考連結

--

--

Albert Hg
learning-from-jhipster

I am a programmer but love other things. I am a nobody but keep myself going. I am a person who wishes to reach the heaven but lost the wings.