萬字長文深入淺出數(shù)據(jù)庫連接池 HikariCP/Commons DBCP/Tomcat/c3p0/druid 對比

拓展閱讀

萬字長文深入淺出數(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ù)庫連接池概覽

c3p0 數(shù)據(jù)池入門使用教程

alibaba druid 入門介紹

數(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)秀開源組件:

  1. HikariCP: HikariCP 是一個高性能的 JDBC 連接池,被廣泛認(rèn)為是目前性能最好的 JDBC 連接池之一。它具有快速啟動、低資源消耗和高性能等特點,適用于各種規(guī)模的應(yīng)用程序。

  2. Apache Commons DBCP: Apache Commons DBCP 是 Apache 軟件基金會的一個子項目,提供了一個可靠的 JDBC 連接池實現(xiàn)。它支持基本的連接池功能,并且易于集成到各種 Java 應(yīng)用程序中。

  3. Tomcat JDBC Pool: Tomcat JDBC Pool 是 Apache Tomcat 項目的一個組件,提供了一個可靠的 JDBC 連接池實現(xiàn)。它專為在 Tomcat 環(huán)境下使用而設(shè)計,但也可以作為獨立的連接池使用。

  4. H2 Database Connection Pool: H2 Database 是一個嵌入式數(shù)據(jù)庫,它也提供了一個簡單而有效的 JDBC 連接池實現(xiàn)。雖然它主要用于嵌入式數(shù)據(jù)庫的應(yīng)用場景,但也可以作為獨立的連接池使用。

  5. c3p0: c3p0 是一個流行的 JDBC 連接池實現(xiàn),具有豐富的配置選項和可靠的性能。它支持連接池的高度定制,并且在很多企業(yè)級應(yīng)用中被廣泛使用。

  6. 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>

代碼

https://github.com/apache/commons-dbcp/tree/HEAD/doc

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)容,可參考

apache commons dbcp2

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)容,可參考

c3p0

tomcat jdbc pool

是什么?

Apache Tomcat DBCP(Database Connection Pool)是一個用于管理數(shù)據(jù)庫連接的組件,通常與Apache Tomcat服務(wù)器一起使用。

它提供了一種機(jī)制來有效地管理數(shù)據(jù)庫連接,以便在高負(fù)載下提供更好的性能和可伸縮性。

以下是Tomcat DBCP的一些關(guān)鍵特性和工作原理:

  1. 連接池管理: Tomcat DBCP通過創(chuàng)建和維護(hù)一組預(yù)先配置的數(shù)據(jù)庫連接來管理連接池。這些連接在需要時可以被應(yīng)用程序使用,并在不再需要時釋放回池中。

  2. 連接池參數(shù)配置: 可以通過Tomcat的配置文件(如context.xml)或者直接在應(yīng)用程序中的代碼中配置連接池的各種參數(shù),例如最大連接數(shù)、最小連接數(shù)、最大等待時間等。

  3. 連接池的工作流程: 當(dāng)應(yīng)用程序需要與數(shù)據(jù)庫進(jìn)行交互時,它從連接池中請求一個數(shù)據(jù)庫連接。如果連接池中有空閑的連接可用,連接池會將一個連接分配給應(yīng)用程序。一旦應(yīng)用程序完成了對數(shù)據(jù)庫的操作,它將連接返回給連接池,以供其他應(yīng)用程序使用。

  4. 連接驗證: Tomcat DBCP可以配置為在從連接池中獲取連接時驗證連接的有效性。這可以通過執(zhí)行簡單的SQL查詢或其他形式的連接測試來實現(xiàn)。這有助于確保從池中獲取的連接是可用和有效的。

  5. 性能優(yōu)化: 通過維護(hù)一組已經(jīng)打開的數(shù)據(jù)庫連接,Tomcat DBCP可以避免在每次數(shù)據(jù)庫請求時都重新創(chuàng)建和銷毀連接,從而提高了性能和效率。

  6. 異常處理: Tomcat DBCP能夠處理數(shù)據(jù)庫連接的異常情況,例如數(shù)據(jù)庫服務(wù)器斷開連接或者連接超時。它會嘗試重新建立連接或者返回錯誤信息,以便應(yīng)用程序能夠適當(dāng)?shù)靥幚磉@些異常情況。

  7. 監(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。

這是有幾個原因的:

  1. 更好的集成: 將連接池功能直接集成到 Tomcat 中可以提供更好的性能和更好的集成。這樣做可以更好地與 Tomcat 內(nèi)部的線程管理、類加載器和上下文生命周期等功能集成,以便提供更一致和更可靠的連接池管理。

  2. 性能優(yōu)化: Apache Tomcat 團(tuán)隊可以更深入地了解 Tomcat 本身的內(nèi)部工作原理,以優(yōu)化連接池的性能,使其更適合與 Tomcat 一起使用。自己實現(xiàn)的連接池可能會針對 Tomcat 的特定需求進(jìn)行優(yōu)化,以提供更好的性能和可靠性。

  3. 更好的控制: 通過開發(fā)自己的連接池實現(xiàn),Apache Tomcat 團(tuán)隊可以更好地控制連接池的開發(fā)和維護(hù)過程。他們可以根據(jù)自己的需求進(jìn)行定制和擴(kuò)展,而不受外部庫的限制。

  4. 解決特定問題: 有時候外部庫可能存在一些限制或者問題,而開發(fā)自己的實現(xiàn)可以更靈活地解決這些問題??赡苁且驗樵谔囟ǖ氖褂们闆r下,已有的庫無法滿足 Tomcat 的需求,或者為了解決一些已知的問題而決定開發(fā)自己的實現(xiàn)。

入門例子

  1. 確保你已經(jīng)在Tomcat的lib目錄中包含 commons-dbcp.jarcommons-pool.jar。

  2. 在你的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>
  1. 在你的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)秀的能力

  1. 高性能:Druid 連接池被設(shè)計為高性能的連接池,具有優(yōu)秀的連接獲取、歸還速度以及低延遲的特點,能夠滿足高并發(fā)的數(shù)據(jù)庫訪問需求。

  2. 實時監(jiān)控:Druid 連接池提供了豐富的實時監(jiān)控功能,能夠?qū)崟r地監(jiān)控連接池的狀態(tài)、性能指標(biāo)以及數(shù)據(jù)庫訪問情況,幫助用戶及時發(fā)現(xiàn)和解決潛在的問題。

  3. 連接池擴(kuò)展:Druid 連接池支持連接池的動態(tài)擴(kuò)展和收縮,能夠根據(jù)實際的數(shù)據(jù)庫訪問負(fù)載自動調(diào)整連接池的大小,提高資源利用率。

  4. SQL防火墻:Druid 連接池內(nèi)置了 SQL 防火墻功能,能夠?qū)τ脩籼峤坏?SQL 進(jìn)行實時的安全檢查和過濾,防止 SQL 注入等安全問題。

  5. 連接泄漏檢測:Druid 連接池能夠檢測連接的泄漏情況,及時發(fā)現(xiàn)并處理連接未正確關(guān)閉的情況,防止因連接泄漏導(dǎo)致的數(shù)據(jù)庫資源浪費和性能下降。

  6. 完善的統(tǒng)計功能:Druid 連接池提供了豐富的統(tǒng)計功能,能夠統(tǒng)計連接池的使用情況、性能指標(biāo)以及數(shù)據(jù)庫訪問情況,幫助用戶深入了解數(shù)據(jù)庫訪問的情況。

  7. 多數(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-01-intro 入門介紹

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),行則將至。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容