拓展閱讀
萬字長文深入淺出數(shù)據(jù)庫連接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 對比
從零開始手寫 mybatis (三)jdbc pool 如何從零手寫實現(xiàn)數(shù)據(jù)庫連接池 dbcp?
萬字長文深入淺出數(shù)據(jù)庫連接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 對比
Database Connection Pool 數(shù)據(jù)庫連接池概覽
數(shù)據(jù)庫連接池 HikariCP 性能為什么這么快?
Apache Tomcat DBCP(Database Connection Pool) 數(shù)據(jù)庫連接池-01-入門介紹
vibur-dbcp 并發(fā)、快速且功能完備的 JDBC 連接池,提供先進(jìn)的性能監(jiān)控功能-01-入門介紹
前言
數(shù)據(jù)庫連接池在日常開發(fā)中幾乎是必備的技能,但是很多知識大多比較零散。
這里老馬為大家簡單做一個匯總,便于查閱學(xué)習(xí)。
連接池的作用
資源重用
由于數(shù)據(jù)庫連接得到重用,避免了頻繁創(chuàng)建、釋放連接引起的大量性能開銷。在減少系統(tǒng)消耗的基礎(chǔ)上,
另一方面也增進(jìn)了系統(tǒng)運行環(huán)境的平穩(wěn)性(減少內(nèi)存碎片以及數(shù)據(jù)庫臨時進(jìn)程/線程的數(shù)量)。
更快的系統(tǒng)響應(yīng)速度
數(shù)據(jù)庫連接池在初始化過程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于池中備用。此時連接的初始化工作均已完成。
對于業(yè)務(wù)請求處理而言,直接利用現(xiàn)有可用連接,避免了數(shù)據(jù)庫連接初始化和釋放過程的時間開銷,從而縮減了系統(tǒng)整體響應(yīng)時間。
新的資源分配手段
對于多應(yīng)用共享同一數(shù)據(jù)庫的系統(tǒng)而言,可在應(yīng)用層通過數(shù)據(jù)庫連接的配置,使用數(shù)據(jù)庫連接池技術(shù)。
設(shè)置某一應(yīng)用最大可用數(shù)據(jù)庫連接數(shù),避免某一應(yīng)用獨占所有數(shù)據(jù)庫資源。
統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄漏
在較為完備的數(shù)據(jù)庫連接池實現(xiàn)中,可根據(jù)預(yù)先設(shè)定的連接占用超時時間,強制收回被超時占用的連接。
從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄漏(當(dāng)程序存在缺陷時,申請的連接忘記關(guān)閉,這時候,就存在連接泄漏了)。

常見的優(yōu)秀開源組件有哪些?
有關(guān)數(shù)據(jù)庫連接池的優(yōu)秀開源組件:
HikariCP: HikariCP 是一個高性能的 JDBC 連接池,被廣泛認(rèn)為是目前性能最好的 JDBC 連接池之一。它具有快速啟動、低資源消耗和高性能等特點,適用于各種規(guī)模的應(yīng)用程序。
Apache Commons DBCP: Apache Commons DBCP 是 Apache 軟件基金會的一個子項目,提供了一個可靠的 JDBC 連接池實現(xiàn)。它支持基本的連接池功能,并且易于集成到各種 Java 應(yīng)用程序中。
Tomcat JDBC Pool: Tomcat JDBC Pool 是 Apache Tomcat 項目的一個組件,提供了一個可靠的 JDBC 連接池實現(xiàn)。它專為在 Tomcat 環(huán)境下使用而設(shè)計,但也可以作為獨立的連接池使用。
H2 Database Connection Pool: H2 Database 是一個嵌入式數(shù)據(jù)庫,它也提供了一個簡單而有效的 JDBC 連接池實現(xiàn)。雖然它主要用于嵌入式數(shù)據(jù)庫的應(yīng)用場景,但也可以作為獨立的連接池使用。
c3p0: c3p0 是一個流行的 JDBC 連接池實現(xiàn),具有豐富的配置選項和可靠的性能。它支持連接池的高度定制,并且在很多企業(yè)級應(yīng)用中被廣泛使用。
Druid: Druid 是阿里巴巴開源的一個數(shù)據(jù)庫連接池實現(xiàn),它不僅提供了連接池功能,還提供了監(jiān)控、統(tǒng)計、防火墻等高級功能。Druid 被廣泛應(yīng)用于大型互聯(lián)網(wǎng)企業(yè)的生產(chǎn)環(huán)境中。
對比
HikariCP 2.6.0、commons-dbcp2 2.1.1、Tomcat 8.0.24、Vibur 16.1、c3p0 0.9.5.2
以下是對上述數(shù)據(jù)庫連接池組件的詳細(xì)對比:
| 特性 | HikariCP | Apache Commons DBCP | Tomcat JDBC Pool | H2 Database Connection Pool | c3p0 | Druid |
|---|---|---|---|---|---|---|
| 性能 | 非常高 | 一般 | 一般 | 一般 | 一般 | 非常高 |
| 配置簡單性 | 高 | 中等 | 中等 | 低 | 中等 | 中等 |
| 可定制性 | 中等 | 中等 | 低 | 低 | 高 | 高 |
| 監(jiān)控和統(tǒng)計功能 | 有 | 無 | 無 | 無 | 無 | 有 |
| 防火墻功能 | 無 | 無 | 無 | 無 | 無 | 有 |
| 社區(qū)活躍度 | 高 | 中等 | 中等 | 低 | 中等 | 高 |
| 適用場景 | 各種場景 | 一般場景 | Tomcat 環(huán)境 | 嵌入式數(shù)據(jù)庫場景 | 各種場景 | 大型互聯(lián)網(wǎng)企業(yè)環(huán)境 |
| 是否支持連接池復(fù)用 | 是 | 是 | 是 | 是 | 是 | 是 |
| 支持的數(shù)據(jù)庫 | 所有主流數(shù)據(jù)庫 | 所有主流數(shù)據(jù)庫 | 所有主流數(shù)據(jù)庫 | H2 Database | 所有主流數(shù)據(jù)庫 | 所有主流數(shù)據(jù)庫 |
看得出來,Druid 和 HikariCP 性能是最優(yōu)異的。
不過別著急,我們慢慢來,先看看其他的。
DBCP組件
介紹
許多Apache項目支持與關(guān)系型數(shù)據(jù)庫進(jìn)行交互。為每個用戶創(chuàng)建一個新連接可能很耗時(通常需要多秒鐘的時鐘時間),以執(zhí)行可能需要毫秒級時間的數(shù)據(jù)庫事務(wù)。對于一個公開托管在互聯(lián)網(wǎng)上的應(yīng)用程序,在同時在線用戶數(shù)量可能非常大的情況下,為每個用戶打開一個連接可能是不可行的。因此,開發(fā)人員通常希望在所有當(dāng)前應(yīng)用程序用戶之間共享一組“池化”的打開連接。在任何給定時間實際執(zhí)行請求的用戶數(shù)量通常只是活躍用戶總數(shù)的非常小的百分比,在請求處理期間是唯一需要數(shù)據(jù)庫連接的時間。應(yīng)用程序本身登錄到DBMS,并在內(nèi)部處理任何用戶賬戶問題。
已經(jīng)有幾個數(shù)據(jù)庫連接池可用,包括Apache產(chǎn)品內(nèi)部和其他地方。這個Commons包提供了一個機(jī)會,來協(xié)調(diào)創(chuàng)建和維護(hù)一個高效、功能豐富的包,以Apache許可證發(fā)布。
commons-dbcp2依賴于commons-pool2中的代碼,以提供底層的對象池機(jī)制。
maven 引入
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
代碼
PoolingDataSourceExample
這里的 datasource 是池化的。
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
public class PoolingDataSourceExample {
public static void main(String[] args) {
//
// First we load the underlying JDBC driver.
// You need this if you don't use the jdbc.drivers
// system property.
//
System.out.println("Loading underlying JDBC driver.");
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("Done.");
//
// Then, we set up the PoolingDataSource.
// Normally this would be handled auto-magically by
// an external configuration, but in this example we'll
// do it manually.
//
System.out.println("Setting up data source.");
DataSource dataSource = setupDataSource(args[0]);
System.out.println("Done.");
//
// Now, we can use JDBC DataSource as we normally would.
//
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
System.out.println("Creating connection.");
conn = dataSource.getConnection();
System.out.println("Creating statement.");
stmt = conn.createStatement();
System.out.println("Executing statement.");
rset = stmt.executeQuery(args[1]);
System.out.println("Results:");
int numcols = rset.getMetaData().getColumnCount();
while(rset.next()) {
for(int i=1;i<=numcols;i++) {
System.out.print("\t" + rset.getString(i));
}
System.out.println("");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rset != null)
rset.close();
} catch (Exception e) {
}
try {
if (stmt != null)
stmt.close();
} catch (Exception e) {
}
try {
if (conn != null)
conn.close();
} catch (Exception e) {
}
}
}
// 這里的 datasource 是池化的。
public static DataSource setupDataSource(String connectURI) {
//
// First, we'll create a ConnectionFactory that the
// pool will use to create Connections.
// We'll use the DriverManagerConnectionFactory,
// using the connect string passed in the command line
// arguments.
//
ConnectionFactory connectionFactory =
new DriverManagerConnectionFactory(connectURI, null);
//
// Next we'll create the PoolableConnectionFactory, which wraps
// the "real" Connections created by the ConnectionFactory with
// the classes that implement the pooling functionality.
//
PoolableConnectionFactory poolableConnectionFactory =
new PoolableConnectionFactory(connectionFactory, null);
//
// Now we'll need a ObjectPool that serves as the
// actual pool of connections.
//
// We'll use a GenericObjectPool instance, although
// any ObjectPool implementation will suffice.
//
ObjectPool<PoolableConnection> connectionPool =
new GenericObjectPool<>(poolableConnectionFactory);
// Set the factory's pool property to the owning pool
poolableConnectionFactory.setPool(connectionPool);
//
// Finally, we create the PoolingDriver itself,
// passing in the object pool we created.
//
PoolingDataSource<PoolableConnection> dataSource =
new PoolingDataSource<>(connectionPool);
return dataSource;
}
}
更多內(nèi)容,可參考
c3p0
是什么?
c3p0是一個易于使用的庫,通過使用jdbc3規(guī)范和jdbc2的可選擴(kuò)展定義的功能來擴(kuò)展傳統(tǒng)JDBC驅(qū)動程序,從而使其“企業(yè)就緒”。
從0.9.5版開始,c3p0完全支持jdbc4規(guī)范。
特別是c3p0提供了一些有用的服務(wù):
一個類,它使傳統(tǒng)的基于DriverManager的JDBC驅(qū)動程序適應(yīng)最新的javax.sql.DataSource方案,以獲取數(shù)據(jù)庫連接。
DataSources后面的Connection和PreparedStatement的透明池可以“包裝”傳統(tǒng)驅(qū)動程序或任意非池化DataSources。
該庫盡力使細(xì)節(jié)正確:
c3p0數(shù)據(jù)源既可引用也可序列化,因此適合綁定到各種基于JNDI的命名服務(wù)。
檢入池中的Connections和Statements時,會仔細(xì)清理Statement和ResultSet,以防止客戶端使用僅清理其Connections的惰性但常見的資源管理策略時資源耗盡。
該庫采用JDBC 2和3規(guī)范定義的方法(即使這些與庫作者的首選項沖突)。
數(shù)據(jù)源以JavaBean樣式編寫,提供了所有必需和大多數(shù)可選屬性(以及一些非標(biāo)準(zhǔn)屬性)以及無參數(shù)構(gòu)造函數(shù)。
實現(xiàn)了所有JDBC定義的內(nèi)部接口(ConnectionPoolDataSource,PooledConnection,生成ConnectionEvent的Connection等)。
您可以將c3p0類與兼容的第三方實現(xiàn)混合使用(盡管并非所有c3p0功能都可以與ConnectionPoolDataSource的外部實現(xiàn)一起使用)。
c3p0希望提供的數(shù)據(jù)源實現(xiàn)不適合大批量“ J2EE企業(yè)應(yīng)用程序”使用。
maven 導(dǎo)入
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
入門代碼
通過代碼顯式指定配置:
ComboPooledDataSource source = new ComboPooledDataSource();
source.setDriverClass("com.mysql.jdbc.Driver");
source.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8");
source.setUser("root");
source.setPassword("123456");
//獲取鏈接
Connection connection = source.getConnection();
System.out.println(connection.getCatalog());
- 日志輸出
七月 17, 2020 4:58:21 下午 com.mchange.v2.log.MLog
信息: MLog clients using java 1.4+ standard logging.
七月 17, 2020 4:58:22 下午 com.mchange.v2.c3p0.C3P0Registry
信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
七月 17, 2020 4:58:22 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1bqqx35abpix6b312lrdzj|7bfcd12c, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1bqqx35abpix6b312lrdzj|7bfcd12c, idleConnectionTestPeriod -> 0, initialPoolSize -> 2, jdbcUrl -> jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 30, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 50, maxStatementsPerConnection -> 0, minPoolSize -> 2, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
test
更多內(nèi)容,可參考
tomcat jdbc pool
是什么?
Apache Tomcat DBCP(Database Connection Pool)是一個用于管理數(shù)據(jù)庫連接的組件,通常與Apache Tomcat服務(wù)器一起使用。
它提供了一種機(jī)制來有效地管理數(shù)據(jù)庫連接,以便在高負(fù)載下提供更好的性能和可伸縮性。
以下是Tomcat DBCP的一些關(guān)鍵特性和工作原理:
連接池管理: Tomcat DBCP通過創(chuàng)建和維護(hù)一組預(yù)先配置的數(shù)據(jù)庫連接來管理連接池。這些連接在需要時可以被應(yīng)用程序使用,并在不再需要時釋放回池中。
連接池參數(shù)配置: 可以通過Tomcat的配置文件(如context.xml)或者直接在應(yīng)用程序中的代碼中配置連接池的各種參數(shù),例如最大連接數(shù)、最小連接數(shù)、最大等待時間等。
連接池的工作流程: 當(dāng)應(yīng)用程序需要與數(shù)據(jù)庫進(jìn)行交互時,它從連接池中請求一個數(shù)據(jù)庫連接。如果連接池中有空閑的連接可用,連接池會將一個連接分配給應(yīng)用程序。一旦應(yīng)用程序完成了對數(shù)據(jù)庫的操作,它將連接返回給連接池,以供其他應(yīng)用程序使用。
連接驗證: Tomcat DBCP可以配置為在從連接池中獲取連接時驗證連接的有效性。這可以通過執(zhí)行簡單的SQL查詢或其他形式的連接測試來實現(xiàn)。這有助于確保從池中獲取的連接是可用和有效的。
性能優(yōu)化: 通過維護(hù)一組已經(jīng)打開的數(shù)據(jù)庫連接,Tomcat DBCP可以避免在每次數(shù)據(jù)庫請求時都重新創(chuàng)建和銷毀連接,從而提高了性能和效率。
異常處理: Tomcat DBCP能夠處理數(shù)據(jù)庫連接的異常情況,例如數(shù)據(jù)庫服務(wù)器斷開連接或者連接超時。它會嘗試重新建立連接或者返回錯誤信息,以便應(yīng)用程序能夠適當(dāng)?shù)靥幚磉@些異常情況。
監(jiān)控和管理: Tomcat DBCP提供了監(jiān)控和管理連接池的功能,可以通過JMX(Java Management Extensions)接口來查看連接池的狀態(tài)、活動連接數(shù)、空閑連接數(shù)等信息,并且可以通過管理工具對連接池進(jìn)行操作。
為什么 tomcat 要自研,而不是用 apache dbcp 這些已有的?
Apache Tomcat 一開始確實使用了像 Commons DBCP 和 Commons Pool 這樣的外部組件來管理數(shù)據(jù)庫連接池。
然而,后來 Apache Tomcat 團(tuán)隊決定開發(fā)自己的連接池實現(xiàn),即 Tomcat DBCP。
這是有幾個原因的:
更好的集成: 將連接池功能直接集成到 Tomcat 中可以提供更好的性能和更好的集成。這樣做可以更好地與 Tomcat 內(nèi)部的線程管理、類加載器和上下文生命周期等功能集成,以便提供更一致和更可靠的連接池管理。
性能優(yōu)化: Apache Tomcat 團(tuán)隊可以更深入地了解 Tomcat 本身的內(nèi)部工作原理,以優(yōu)化連接池的性能,使其更適合與 Tomcat 一起使用。自己實現(xiàn)的連接池可能會針對 Tomcat 的特定需求進(jìn)行優(yōu)化,以提供更好的性能和可靠性。
更好的控制: 通過開發(fā)自己的連接池實現(xiàn),Apache Tomcat 團(tuán)隊可以更好地控制連接池的開發(fā)和維護(hù)過程。他們可以根據(jù)自己的需求進(jìn)行定制和擴(kuò)展,而不受外部庫的限制。
解決特定問題: 有時候外部庫可能存在一些限制或者問題,而開發(fā)自己的實現(xiàn)可以更靈活地解決這些問題??赡苁且驗樵谔囟ǖ氖褂们闆r下,已有的庫無法滿足 Tomcat 的需求,或者為了解決一些已知的問題而決定開發(fā)自己的實現(xiàn)。
入門例子
確保你已經(jīng)在Tomcat的
lib目錄中包含commons-dbcp.jar和commons-pool.jar。在你的Web應(yīng)用程序的
WEB-INF目錄下創(chuàng)建一個名為context.xml的文件,并在其中配置數(shù)據(jù)庫連接池。
以下是一個示例context.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="#{username}" password="#{password}"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/#{database}"/>
</Context>
- 在你的Web應(yīng)用程序中,你可以通過JNDI查找來獲取數(shù)據(jù)庫連接。以下是一個簡單的Servlet示例,演示如何獲取數(shù)據(jù)庫連接并執(zhí)行查詢:
import java.io.*;
import java.sql.*;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.sql.*;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Connection conn = null;
try {
// 查找上下文中的數(shù)據(jù)庫連接池
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/TestDB");
// 從連接池獲取連接
conn = ds.getConnection();
// 執(zhí)行查詢
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM your_table");
while (rs.next()) {
out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
out.println("<br/>");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關(guān)閉連接
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
拓展閱讀:
更多細(xì)節(jié),參見 tomcat dbcp
vibur dbcp
是什么?
Vibur DBCP 是一個并發(fā)、快速且功能完備的 JDBC 連接池,提供先進(jìn)的性能監(jiān)控功能,包括慢 SQL 查詢的檢測和記錄、應(yīng)用線程的非饑餓保證、語句緩存以及與 Hibernate 集成等特性。
該項目主頁包含了對所有 Vibur 特性和配置選項的詳細(xì)描述,以及與 Hibernate 和 Spring 的各種配置示例等內(nèi)容。
Vibur DBCP 基于 Vibur Object Pool 構(gòu)建,后者是一個通用的并發(fā) Java 對象池。
特性
主要特點一覽
確保沒有線程會被排除在訪問 JDBC 連接池連接之外。參見 poolFair 配置參數(shù)。
檢測和記錄慢 SQL 查詢、大于預(yù)期的 ResultSet 和持續(xù)時間較長的 getConnection() 方法調(diào)用。查看相關(guān)的配置屬性 這里 和 這里。
支持 Hibernate 3.6、4.x 和 5.x 的集成。
對 JDBC Statement(Prepared 和 Callable)進(jìn)行緩存支持。
使用標(biāo)準(zhǔn) Java 并發(fā)工具和動態(tài)代理構(gòu)建,不使用任何 synchronized 塊或方法。
Vibur DBCP 需要 Java 1.6+,并且僅有以下外部依賴項:其專用對象池、slf4j/log4j 和 ConcurrentLinkedHashMap。CLHM 依賴項是可
選的,只有在啟用/使用 JDBC Statement 緩存時,應(yīng)用程序才需要提供它。
其他特點
智能池大小調(diào)整 - 根據(jù)最近使用的連接數(shù)量的啟發(fā)式方法,可以減少 JDBC 池中的空閑連接數(shù)量。
支持驗證間隔;即,在每次使用之前,從 JDBC 池獲取的連接并不會被驗證,只有在連接上一次使用后經(jīng)過一定時間后才會進(jìn)行驗證。
可以通過調(diào)用代理的 unwrap 方法從相應(yīng)的代理對象中檢索原始 JDBC 連接或 Statement 對象。
為當(dāng)前獲取的所有 JDBC 連接提供記錄(通過 JMX 或日志文件),包括它們被獲取時的堆棧跟蹤;如果調(diào)試丟失/未關(guān)閉的連接或者應(yīng)用程序
想知道當(dāng)前所有連接的來源,這將非常有用。JMX 支持 - 池注冊了一個 MBean,通過它可以觀察和/或設(shè)置各種池參數(shù)。
maven 依賴
<dependency>
<groupId>org.vibur</groupId>
<artifactId>vibur-dbcp</artifactId>
<version>25.0</version>
</dependency>
Spring with Hibernate 3.6/4.x/5.x Configuration Snippet
<!-- Vibur DBCP dataSource bean definition: -->
<bean id="dataSource" class="org.vibur.dbcp.ViburDBCPDataSource" init-method="start" destroy-method="terminate">
<property name="jdbcUrl" value="jdbc:hsqldb:mem:sakila;shutdown=false"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="poolInitialSize">10</property>
<property name="poolMaxSize">100</property>
<property name="connectionIdleLimitInSeconds">30</property>
<property name="testConnectionQuery">isValid</property>
<property name="logQueryExecutionLongerThanMs" value="500"/>
<property name="logStackTraceForLongQueryExecution" value="true"/>
<property name="statementCacheMaxSize" value="200"/>
</bean>
<!-- For Hibernate5 set the sessionFactory class below to org.springframework.orm.hibernate5.LocalSessionFactoryBean -->
<!-- For Hibernate4 set the sessionFactory class below to org.springframework.orm.hibernate4.LocalSessionFactoryBean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="the.project.packages"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
</props>
</property>
</bean>
<!-- For Hibernate5 set the transactionManager class below to org.springframework.orm.hibernate5.HibernateTransactionManager -->
<!-- For Hibernate4 set the transactionManager class below to org.springframework.orm.hibernate4.HibernateTransactionManager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
Programming Configuration Snippet
public DataSource createDataSourceWithStatementsCache() {
ViburDBCPDataSource ds = new ViburDBCPDataSource();
ds.setJdbcUrl("jdbc:hsqldb:mem:sakila;shutdown=false");
ds.setUsername("sa");
ds.setPassword("");
ds.setPoolInitialSize(10);
ds.setPoolMaxSize(100);
ds.setConnectionIdleLimitInSeconds(30);
ds.setTestConnectionQuery("isValid");
ds.setLogQueryExecutionLongerThanMs(500);
ds.setLogStackTraceForLongQueryExecution(true);
ds.setStatementCacheMaxSize(200);
ds.start();
return ds;
}
vibur 的性能相比較其他的,算是比較優(yōu)異的。但是 github star 比較少。
配置項特別多,感興趣的話 參見 vibur dbcp
alibaba druid
這個在國內(nèi)應(yīng)該算是家喻戶曉了,介紹的文章非常多,這里只做簡單介紹。
是什么
Druid是Java語言中最好的數(shù)據(jù)庫連接池。Druid能夠提供強大的監(jiān)控和擴(kuò)展功能。
PS:國內(nèi)的話,還是非常推薦使用這個的。
一些優(yōu)秀的能力
高性能:Druid 連接池被設(shè)計為高性能的連接池,具有優(yōu)秀的連接獲取、歸還速度以及低延遲的特點,能夠滿足高并發(fā)的數(shù)據(jù)庫訪問需求。
實時監(jiān)控:Druid 連接池提供了豐富的實時監(jiān)控功能,能夠?qū)崟r地監(jiān)控連接池的狀態(tài)、性能指標(biāo)以及數(shù)據(jù)庫訪問情況,幫助用戶及時發(fā)現(xiàn)和解決潛在的問題。
連接池擴(kuò)展:Druid 連接池支持連接池的動態(tài)擴(kuò)展和收縮,能夠根據(jù)實際的數(shù)據(jù)庫訪問負(fù)載自動調(diào)整連接池的大小,提高資源利用率。
SQL防火墻:Druid 連接池內(nèi)置了 SQL 防火墻功能,能夠?qū)τ脩籼峤坏?SQL 進(jìn)行實時的安全檢查和過濾,防止 SQL 注入等安全問題。
連接泄漏檢測:Druid 連接池能夠檢測連接的泄漏情況,及時發(fā)現(xiàn)并處理連接未正確關(guān)閉的情況,防止因連接泄漏導(dǎo)致的數(shù)據(jù)庫資源浪費和性能下降。
完善的統(tǒng)計功能:Druid 連接池提供了豐富的統(tǒng)計功能,能夠統(tǒng)計連接池的使用情況、性能指標(biāo)以及數(shù)據(jù)庫訪問情況,幫助用戶深入了解數(shù)據(jù)庫訪問的情況。
多數(shù)據(jù)源支持:Druid 連接池支持多種類型的數(shù)據(jù)庫,包括 MySQL、Oracle、PostgreSQL 等,能夠靈活適應(yīng)不同類型的數(shù)據(jù)庫訪問需求。
maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
配置
DruidDataSource大部分屬性都是參考DBCP的,如果你原來就是使用DBCP,遷移是十分方便的。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="6000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
<property name="asyncInit" value="true" />
</bean>
這個是 spring 的配置,其實配置上就是一個 POJO
感興趣的話,可以拓展一下:
alibaba druid-02-FAQ druid 常見問題
druid+mysql 個人實戰(zhàn)例子
這里以 durid 做一個實戰(zhàn)例子,其他的也都大同小異。
mysql 數(shù)據(jù)準(zhǔn)備
建表語句
use test;
CREATE TABLE "users" (
"id" int(11) NOT NULL,
"username" varchar(255) NOT NULL,
"email" varchar(255) NOT NULL,
PRIMARY KEY ("id")
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
插入數(shù)據(jù)
insert into users (id, username, email) values (1, 'u-1', '1@email.com');
insert into users (id, username, email) values (2, 'u-2', '2@email.com');
insert into users (id, username, email) values (3, 'u-3', '3@email.com');
數(shù)據(jù)確認(rèn):
mysql> select * from users;
+----+----------+-------------+
| id | username | email |
+----+----------+-------------+
| 1 | u-1 | 1@email.com |
| 2 | u-2 | 2@email.com |
| 3 | u-3 | 3@email.com |
+----+----------+-------------+
3 rows in set (0.00 sec)
數(shù)據(jù)庫準(zhǔn)備
maven 引入
<!-- MySQL JDBC Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version> <!-- 或者最新版本 -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
入門代碼
package com.github.houbb.calcite.learn.mysql;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* druid 整合 mysql 使用
* @author 老馬嘯西風(fēng)
*/
public class DruidMySQLExample {
public static void main(String[] args) {
// 初始化 Druid 數(shù)據(jù)源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
dataSource.setUsername("admin");
dataSource.setPassword("123456");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 從連接池獲取數(shù)據(jù)庫連接
conn = dataSource.getConnection();
// 創(chuàng)建 Statement 對象
stmt = conn.createStatement();
// 執(zhí)行 SQL 查詢
rs = stmt.executeQuery("SELECT * FROM users");
// 遍歷結(jié)果集
while (rs.next()) {
// 處理每一行數(shù)據(jù)
int id = rs.getInt("id");
String username = rs.getString("username");
String email = rs.getString("email");
// 輸出到控制臺
System.out.println("ID: " + id + ", username: " + username+ ", email: " + email);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 關(guān)閉資源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
輸出如下:
ID: 1, username: u-1, email: 1@email.com
ID: 2, username: u-2, email: 2@email.com
ID: 3, username: u-3, email: 3@email.com
我本來以為 druid 已經(jīng)天下無敵了,沒想到 HikariCP 更加勇猛。
HikariCP 是誰的部將?
HikariCP
是什么?
快速、簡單、可靠。HikariCP 是一個“零額外開銷”的生產(chǎn)就緒的 JDBC 連接池。
該庫大小約為130Kb,非常輕量級。
構(gòu)件 Artifacts
Java 11+ Maven 構(gòu)件
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
Java 8 Maven 構(gòu)件 (維護(hù)模式)
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
配置使用
這個返回比較簡單,都是統(tǒng)一的。
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
config.setUsername("bart");
config.setPassword("51mp50n");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
Hikari 為什么這么快?
為什么 Hikari 可以做到性能基本無損耗?到底是如何實現(xiàn)的。
這也是本文的重點,也是老馬為什么寫這篇文章的原因。
?? 我們深入到你的字節(jié)碼中
為了使 HikariCP 的速度達(dá)到目前的水平,我們進(jìn)行了字節(jié)碼級別的工程處理,甚至更進(jìn)一步。我們采用了我們所知的所有技巧來幫助 JIT 幫助您。
我們研究了編譯器的字節(jié)碼輸出,甚至 JIT 的匯編輸出,以將關(guān)鍵的程序例程限制在 JIT 內(nèi)聯(lián)閾值以下。
我們展平了繼承層次結(jié)構(gòu),隱藏了成員變量,消除了類型轉(zhuǎn)換。
?? 微優(yōu)化
HikariCP 包含許多微優(yōu)化,單獨看每個優(yōu)化幾乎無法測量,但總體上對性能有所提升。其中一些優(yōu)化是以每百萬次調(diào)用攤銷的毫秒為單位進(jìn)行度量的。
ArrayList
一個非常重要(就性能而言)的優(yōu)化是在用于跟蹤打開的 Statement 實例的 ConnectionProxy 中消除對 ArrayList<Statement> 實例的使用。
當(dāng)關(guān)閉 Statement 時,必須從此集合中刪除它,當(dāng)關(guān)閉 Connection 時,必須迭代該集合并關(guān)閉任何打開的 Statement 實例,最后必須清空該集合。對于一般用途而言,Java 的 ArrayList 每次執(zhí)行 get(int index) 調(diào)用時都會進(jìn)行范圍檢查,這是明智的做法。然而,由于我們可以對我們的范圍提供保證,所以這個檢查只是額外開銷。
此外,remove(Object) 實現(xiàn)執(zhí)行從頭到尾的掃描,然而 JDBC 編程中常見的模式是在使用后立即關(guān)閉 Statement,或者按打開順序的相反順序關(guān)閉。對于這些情況,從尾部開始的掃描將執(zhí)行得更好。
因此,ArrayList<Statement> 被替換為一個自定義類 FastList,它消除了范圍檢查,并執(zhí)行從尾部到頭部的移除掃描。
PS:這一點在很多工具中可以簡單,相對是一個可以想到的優(yōu)化方案。
ConcurrentBag
HikariCP 包含一個名為 ConcurrentBag 的自定義無鎖集合。這個想法是從 C# .NET 的 ConcurrentBag 類借來的,但內(nèi)部實現(xiàn)是相當(dāng)不同的。
ConcurrentBag 提供...
無鎖設(shè)計
線程本地緩存
隊列竊取
直接傳遞優(yōu)化
...這導(dǎo)致了高度并發(fā)性、極低的延遲和最小化的偽共享現(xiàn)象的發(fā)生。
PS: 無鎖乃是加鎖的最高境界,值得以后統(tǒng)一深入學(xué)習(xí)一下。
調(diào)用:invokevirtual vs invokestatic
為了為 Connection、Statement 和 ResultSet 實例生成代理,HikariCP 最初使用一個單例工廠,ConnectionProxy 的情況下保存在靜態(tài)字段(PROXY_FACTORY)中。
以下是十多個類似以下方法的方法:
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}
使用原始的單例工廠,生成的字節(jié)碼如下所示:
public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=5, locals=3, args_size=3
0: getstatic #59 // Field PROXY_FACTORY:Lcom/zaxxer/hikari/proxy/ProxyFactory;
3: aload_0
4: aload_0
5: getfield #3 // Field delegate:Ljava/sql/Connection;
8: aload_1
9: aload_2
10: invokeinterface #74, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
15: invokevirtual #69 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
18: return
可以看到首先是對靜態(tài)字段 PROXY_FACTORY 的 getstatic 調(diào)用,以及(最后)對 ProxyFactory 實例上的 getProxyPreparedStatement() 的 invokevirtual 調(diào)用。
我們消除了單例工廠(由 Javassist 生成)并用具有靜態(tài)方法的最終類替換了它(其方法體由 Javassist 生成)。
Java 代碼變?yōu)椋?/p>
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}
其中 getProxyPreparedStatement() 是在 ProxyFactory 類中定義的靜態(tài)方法。生成的字節(jié)碼如下所示:
private final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=4, locals=3, args_size=3
0: aload_0
1: aload_0
2: getfield #3 // Field delegate:Ljava/sql/Connection;
5: aload_1
6: aload_2
7: invokeinterface #72, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
12: invokestatic #67 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
15: areturn
這里有三件事值得注意:
getstatic 調(diào)用消失了。
invokevirtual 調(diào)用被替換為更容易由 JVM 優(yōu)化的 invokestatic 調(diào)用。
最后,可能乍一看沒有注意到的是,棧的大小從 5 個元素減少到 4 個元素。這是因為在 invokevirtual 的情況下,隱式傳遞了 ProxyFactory 實例(即 this)到棧上,并且在調(diào)用 getProxyPreparedStatement() 時棧上的值還有一個額外的(看不見的)彈出操作。
總的來說,這個變化消除了一個靜態(tài)字段訪問,一個推送和從棧中彈出的操作,并且由于調(diào)用點保證不會更改,使得調(diào)用更容易由 JIT 進(jìn)行優(yōu)化。
PS: 老實說,這個優(yōu)化點實在是太高了,有 15 樓那么高,一般開發(fā)者根本不會有這個高度。
ˉ_(ツ)_/ˉ 是的,但還是...
在我們的基準(zhǔn)測試中,顯然我們正在運行針對一個存根 JDBC 驅(qū)動程序?qū)崿F(xiàn),因此 JIT 進(jìn)行了大量內(nèi)聯(lián)。
然而,在基準(zhǔn)測試中,其他連接池也在存根級別進(jìn)行相同的內(nèi)聯(lián)。所以,對我們來說沒有固有的優(yōu)勢。
但是,在使用真實驅(qū)動程序時,內(nèi)聯(lián)肯定是方程式的重要部分,這引出了另一個話題...
? 調(diào)度器量子
一些輕松的閱讀材料。
總結(jié)起來,顯然,當(dāng)你“同時”運行 400 個線程時,除非你有 400 個核心,否則你實際上并沒有“同時”運行它們。操作系統(tǒng),利用 N 個 CPU 核心,在你的線程之間切換,給每個線程一個小的“切片”時間來運行,稱為量子。
在許多應(yīng)用程序中運行大量線程時,當(dāng)你的時間片用完時(作為一個線程),可能要“很長時間”才能再次得到調(diào)度程序的運行機(jī)會。因此,在其時間片內(nèi),線程盡可能多地完成工作,避免強制放棄時間片的鎖,否則將會產(chǎn)生性能損失。而且不是一點點。
這就引出了...
?? CPU 緩存行失效
當(dāng)你無法在量子內(nèi)完成工作時,另一個很大的影響就是 CPU 緩存行失效。
如果你的線程被調(diào)度程序搶占,當(dāng)它再次有機(jī)會運行時,它經(jīng)常訪問的所有數(shù)據(jù)很可能不再位于核心的 L1 或核心對的 L2 緩存中。更有可能是因為你無法控制下次將被調(diào)度到哪個核心。
這兩點涉及到一些計算機(jī)本身的知識,感興趣的話,可以看一下老馬的翻譯文章:
HikariCP 拓展閱讀之偽共享 (False sharing)
HikariCP 拓展閱讀 cpu 調(diào)度 / CPU Scheduling
偽共享這一點以前李大狗的數(shù)據(jù)結(jié)構(gòu)源碼解析中也提到過,算得上是優(yōu)化底層的老油條了。
數(shù)據(jù)源寫到這里基本結(jié)束了,但是呢。
紙上得來終覺淺,絕知此事要躬行。
如果讓我們自己實現(xiàn)一個 dbcp 數(shù)據(jù)庫連接池呢?
簡單版手動實現(xiàn)
自己實現(xiàn)一個簡化版,便于理解原理。
簡單實現(xiàn)
- 連接池接口
public interface IPool {
/**
* 獲取新的數(shù)據(jù)庫鏈接
* @return 數(shù)據(jù)庫鏈接
*/
PoolConnection getPoolConnection();
}
其中 PoolConnection 如下:
public class PoolConnection {
/**
* 是否繁忙
*/
private volatile boolean isBusy;
/**
* 數(shù)據(jù)庫鏈接信息
*/
private Connection connection;
}
- 核心實現(xiàn)
public class PoolImpl implements IPool {
/**
* 數(shù)據(jù)庫驅(qū)動
*/
private final String jdbcDriver;
/**
* 數(shù)據(jù)庫連接
*/
private final String jdbcUrl;
/**
* 數(shù)據(jù)庫用戶名
*/
private final String username;
/**
* 數(shù)據(jù)庫密碼
*/
private final String passowrd;
/**
* 連接池大小
*/
private final int size;
/**
* 數(shù)據(jù)庫連接池列表
*/
private List<PoolConnection> poolConnections = new ArrayList<>();
public PoolImpl(String jdbcDriver, String jdbcUrl, String username, String passowrd, int size) {
this.jdbcDriver = jdbcDriver;
this.jdbcUrl = jdbcUrl;
this.username = username;
this.passowrd = passowrd;
this.size = size;
init();
}
private void init() {
try {
//1. 注冊數(shù)據(jù)庫連接信息
Driver sqlDriver = (Driver) Class.forName(jdbcDriver).newInstance();
DriverManager.registerDriver(sqlDriver);
//2. 初始化連接池
initConnectionPool();
} catch (InstantiationException | IllegalAccessException | SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 初始化鏈接
* @throws SQLException sql 異常
*/
private void initConnectionPool() throws SQLException {
for(int i = 0; i < size; i++) {
Connection connection = DriverManager.getConnection(jdbcUrl, username, passowrd);
PoolConnection poolConnection = new PoolConnection(false, connection);
poolConnections.add(poolConnection);
}
}
@Override
public PoolConnection getPoolConnection() {
if(poolConnections.size() <= 0) {
return null;
}
PoolConnection poolConnection = getRealConnection();
while (poolConnection == null) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
poolConnection = getRealConnection();
}
return poolConnection;
}
/**
* 獲取數(shù)據(jù)庫鏈接對象
* @return 數(shù)據(jù)庫鏈接對象
*/
private synchronized PoolConnection getRealConnection() {
for(PoolConnection poolConnection : poolConnections) {
// 尋找不處于繁忙狀態(tài)的連接
if(!poolConnection.isBusy()) {
Connection connection = poolConnection.getConnection();
// 測試當(dāng)前連接是否有效
try {
if(!connection.isValid(5000)) {
Connection validConnection = DriverManager.getConnection(jdbcUrl, username, passowrd);
poolConnection.setConnection(validConnection);
}
} catch (SQLException e) {
e.printStackTrace();
}
// 設(shè)置為繁忙
poolConnection.setBusy(true);
return poolConnection;
}
}
return null;
}
}
- 線程池管理類
使用單例
public class PoolManager {
/**
* 連接池持有類
*/
private static class PoolHolder {
private static String url = "";
private static String driver = "";
private static String username = "";
private static String password = "";
private static int size = 10;
private static IPool poolImpl = new PoolImpl(driver, url, username, password, size);
}
/**
* 內(nèi)部類單利模式產(chǎn)生使用對象
* @return 單例
*/
public static IPool getInstance() {
return PoolHolder.poolImpl;
}
}
當(dāng)然,上面的例子過于淺嘗輒止,想深入學(xué)習(xí),可以參考下下面的文章。
第一節(jié) 從零開始手寫 mybatis(一)MVP 版本。
第二節(jié) 從零開始手寫 mybatis(二)mybatis interceptor 插件機(jī)制詳解
第三節(jié) 從零開始手寫 mybatis(三)jdbc pool 從零實現(xiàn)數(shù)據(jù)庫連接池
第四節(jié) 從零開始手寫 mybatis(四)- mybatis 事務(wù)管理機(jī)制詳解
小結(jié)
數(shù)據(jù)庫連接池在國內(nèi)主流還是 druid,但是 HikariCP 可謂在設(shè)計上精益求精,值得我們深入學(xué)習(xí)其理念。
山高路遠(yuǎn),行則將至。