7.1 連接池

今天我們將學習如何更專業地使用數據庫。現在我們將討論線程池,或者英文的連接池。

連接到數據庫需要一些時間。特別是如果數據庫是遠程的。如果我們為每個請求都連接到數據庫,那麼我們的應用程序的響應速度將非常慢。更不用說它會消耗的資源了。

作為此類問題的解決方案,有人提出將與基數的連接存儲在某個集合中,該集合稱為線程池。

當我們請求一個新的連接時,連接池會創建它,關閉時並不會關閉它,而是保存在連接池中。如果我們再次從連接池請求連接,它將給我們一個舊的,而不是創建一個新的。

事實上,該應用程序通過一個特殊的驅動程序工作,該驅動程序是常規 JDBC 驅動程序的包裝器,並且具有用於處理池的附加功能。這種情況可以表示如下:

連接池

程序員可以手動設置連接池的設置:活動連接數、超時等。

對於特別極端的情況,您可以編寫自己的連接池:一個包含連接列表的類。它將覆蓋關閉函數,這會將連接返回到列表,並且會有許多其他好東西,如打開連接計時器。當長時間沒有連接時,關閉連接。

7.2* 接口DataSource和ConnectionPoolDataSource

線程池通常與數據源對象協同工作。這個對象可以被認為是遠程數據庫的抽象。數據源這個名字可以直譯為數據源。這是一種暗示。

DataSource 對像用於獲取與數據庫的物理連接,是 DriverManager 的替代方法。無需註冊 JDBC 驅動程序。您只需要設置適當的參數來建立連接和執行 getConnection() 方法.

在本地(直接在應用程序中)創建 DataSource 類型的對象時,連接參數由 JDBC 驅動程序提供者提供的適當方法設置。這些方法不是由 DataSource 接口定義的,因為連接到來自不同供應商的 DBMS 的參數在類型和數量上可能不同(例如,並非所有 DBMS 都需要指定驅動程序或網絡協議的類型)。

因此,在使用Oracle DBMS 時,要使用相應的set 和get 方法,就需要獲得一個OracleDataSource 類型的對象,該對像是實現了DataSource 接口的同名類的實例。因此,這種創建 DataSource 類型對象的方式使您的代碼的可移植性降低,並且對驅動程序的具體實現的依賴性降低。

下面的代碼示例說明了 DataSource 類型對象的本地使用。

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;

public class DataSource {
    public static void main(String[] args) {
    	try {
        	OracleDataSource ods = new OracleDataSource();

        	ods.setUser("root");
        	ods.setPassword("secret");
        	ods.setDriverType("thin");
        	ods.setDatabaseName("test");
        	ods.setServerName("localhost");
        	ods.setPortNumber(1521);

        	Connection connection = ods.getConnection();
        	System.out.println("Connection successful!!!");

    	} catch (SQLException se) {
        	se.printStackTrace();
    	}
    	System.out.println("Goodbye!");
	}
}

7.3* JNDI 接口

DataSource 類型對象的全部功能與 JNDI 接口的使用相結合。JNDI 是一種用於大型服務器應用程序的特殊服務(類似於目錄),它允許一個服務與另一個服務建立連接。

使用名稱和目錄服務允許您存儲以前由系統管理員使用預定義的連接參數創建的 DataSource 類型的對象。以下是 Sun 開發的一些標準屬性(參數)名稱:

物業名稱 Java數據類型 目的
數據庫名稱 細繩 數據庫名稱
服務器名稱 細繩 服務器名稱
用戶 細繩 用戶名(ID)
密碼 細繩 用戶密碼
端口號 整數 DBMS 服務器端口號

DataSource 和JNDI 接口的結合在開發基於J2EE 組件技術的多層企業應用程序中起著關鍵作用。

如果您在應用程序代碼中結合使用 DataSource 和 JNDI 接口,則只需從命名和目錄服務請求一個 DataSource 類型的對象。在這種情況下,連接的詳細信息和依賴於特定驅動程序實現的程序代碼對應用程序隱藏在名稱和目錄服務中存儲的對像中。

因此,DataSource 和 JNDI 類型對象的共享涉及兩個相互獨立執行的步驟:

  1. 您必須使用Context.bind().javax.naming
  2. 使用Context.lookup(). 之後,您可以使用它的方法DataSource.getConnection()來獲得與數據庫的連接。

以下是一起使用 JNDI 接口和 OracleDataSource 類型對象的示例。

// Create a key JNDI object
Hashtable env = new Hashtable();
env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
Context ctx = new InitialContext(env);

// Get the DataSource object by its name
DataSource ods = (DataSource) ctx.lookup("myDatabase");

// Get the Connection from the DataSource object
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");

我們不會分析 JNDI 的工作。這超出了本課程的範圍。我只想讓您知道,這種方法在大型應用程序中非常普遍。如果您以後看到這樣的代碼,請不要迷路。

它是如何工作的並不重要。您只需要知道,通過 JNDI 服務,您可以獲得在服務目錄中註冊的任何服務的代理對象。這就是您獲取 DataSource 對象並使用它來處理數據庫的方式。

7.4 連接池示例

讓我們回到我們開始的地方——連接池。

Java 程序員在他們的程序中經常使用包含各種連接池實現的庫。其中最受歡迎的有以下三種:

  • Apache Commons DBCP
  • C3PO
  • 光CP

Apache 項目是第一個創建良好連接池的項目,它也在 Tomcat 網絡服務器內部使用。使用它的一個例子:

public class DBCPDataSource {

    private static BasicDataSource ds = new BasicDataSource();
    static {
    	ds.setUrl("jdbc:h2:mem:test");
    	ds.setUsername("user");
    	ds.setPassword("password");
    	ds.setMinIdle(5);
    	ds.setMaxIdle(10);
    	ds.setMaxOpenPreparedStatements(100);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private DBCPDataSource(){ }
}

第二個非常受歡迎的礦池是C3PO。奇怪的名字,我同意。C3PO 這個名字是參考了星球大戰中的 c3po 機器人。CP 也是 Connection Pool 的縮寫。

public class C3p0DataSource {

    private static ComboPooledDataSource cpds = new ComboPooledDataSource();
    static {
    	try {
        	cpds.setDriverClass("org.h2.Driver");
        	cpds.setJdbcUrl("jdbc:h2:mem:test");
        	cpds.setUser("user");
        	cpds.setPassword("password");
    	} catch (PropertyVetoException e) {
            // handle the exception
    	}
    }

    public static Connection getConnection() throws SQLException {
    	return cpds.getConnection();
    }

    private C3p0DataSource(){}
}

它的文檔可以在官方頁面上找到。

最後,我們這個時代最流行的連接池是HikariCP

public class HikariCPDataSource {

    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;

    static {
    	config.setJdbcUrl("jdbc:h2:mem:test");
    	config.setUsername("user");
    	config.setPassword("password");
    	config.addDataSourceProperty("cachePrepStmts", "true");
    	config.addDataSourceProperty("prepStmtCacheSize", "250");
    	config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    	ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private HikariCPDataSource(){}
}

這是他的官方GitHub頁面。

還有一篇關於配置的好文章