更新時間:2022-09-02 11:46:06 來源:動力節點 瀏覽1555次
Java連接池是什么?動力節點小編來為大家解答。
連接池是一種眾所周知的數據訪問模式。其主要目的是減少執行數據庫連接和讀/寫數據庫操作所涉及的開銷。
在最基本的層面上, 連接池是一種數據庫連接緩存實現 ,可以根據特定需求進行配置。
在本教程中,我們將討論一些流行的連接池框架。然后我們將學習如何從頭開始實現我們自己的連接池。
當然,這個問題是修辭性的。
如果我們分析典型數據庫連接生命周期中涉及的步驟順序,我們就會明白為什么:
使用數據庫驅動程序打開到數據庫的連接
打開TCP 套接字以讀取/寫入數據
通過套接字讀取/寫入數據
關閉連接
關閉插座
很明顯,數據庫連接是相當昂貴的操作,因此,在每個可能的用例中都應該減少到最低限度(在邊緣情況下,只是避免)。
這就是連接池實現發揮作用的地方。
通過簡單地實現一個數據庫連接容器,它允許我們重用許多現有的連接,我們可以有效地節省執行大量昂貴的數據庫旅行的成本。這提高了我們的數據庫驅動應用程序的整體性能。
從實用的角度來看,考慮到已經可用的“企業就緒”連接池框架的數量,從頭開始實施連接池是沒有意義的。
從教學的角度來看,這是本文的目標,事實并非如此。
盡管如此,在我們學習如何實現一個基本的連接池之前,我們將首先展示一些流行的連接池框架。
1.Apache Commons DBCP
讓我們從Apache Commons DBCP Component開始,這是一個功能齊全的連接池 JDBC 框架:
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(){ }
}
在這種情況下,我們使用帶有靜態塊的包裝類來輕松配置 DBCP 的屬性。
以下是如何獲得與DBCPDataSource類的池連接:
Connection con = DBCPDataSource.getConnection();
2.HikariCP
現在讓我們看看HikariCP ,這是一個由Brett Wooldridge創建的閃電般快速的 JDBC 連接池框架
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(){}
}
同樣,這里是如何獲得與HikariCPDataSource類的池連接:
Connection con = HikariCPDataSource.getConnection();
3.C3P0
這篇評論的最后一篇是C3P0,這是一個由 Steve Waldman 開發的強大的 JDBC4 連接和語句池框架:
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(){}
}
正如預期的那樣,使用C3p0DataSource類獲取池連接類似于前面的示例:
Connection con = C3p0DataSource.getConnection();
為了更好地理解連接池的底層邏輯,讓我們創建一個簡單的實現。
我們將從僅基于一個單一接口的松散耦合設計開始:
public interface ConnectionPool {
Connection getConnection();
boolean releaseConnection(Connection connection);
String getUrl();
String getUser();
String getPassword();
}
ConnectionPool接口定義了基本連接池的公共 API 。
現在讓我們創建一個提供一些基本功能的實現,包括獲取和釋放池連接:
public class BasicConnectionPool
implements ConnectionPool {
private String url;
private String user;
private String password;
private List<Connection> connectionPool;
private List<Connection> usedConnections = new ArrayList<>();
private static int INITIAL_POOL_SIZE = 10;
public static BasicConnectionPool create(
String url, String user,
String password) throws SQLException {
List<Connection> pool = new ArrayList<>(INITIAL_POOL_SIZE);
for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
pool.add(createConnection(url, user, password));
}
return new BasicConnectionPool(url, user, password, pool);
}
// standard constructors
@Override
public Connection getConnection() {
Connection connection = connectionPool
.remove(connectionPool.size() - 1);
usedConnections.add(connection);
return connection;
}
@Override
public boolean releaseConnection(Connection connection) {
connectionPool.add(connection);
return usedConnections.remove(connection);
}
private static Connection createConnection(
String url, String user, String password)
throws SQLException {
return DriverManager.getConnection(url, user, password);
}
public int getSize() {
return connectionPool.size() + usedConnections.size();
}
// standard getters
}
雖然非常幼稚,但BasicConnectionPool類提供了我們期望從典型連接池實現中獲得的最小功能。
簡而言之,該類基于存儲10個連接的ArrayList初始化一個連接池,可以方便地重用。
還可以使用DriverManager類和Datasource實現創建 JDBC 連接。
由于保持連接數據庫的創建不可知要好得多,我們在create()靜態工廠方法中使用了前者。
在這種情況下,我們將方法放在BasicConnectionPool中 ,因為這是接口的唯一實現。
在具有多個ConnectionPool實現的更復雜的設計中,最好將其放置在接口中,從而獲得更靈活的設計和更高水平的內聚。
這里要強調的最相關的一點是,一旦創建了池,就會從池中獲取連接,因此無需創建新的。
此外,當一個連接被釋放時,它實際上會返回到池中,因此其他客戶端可以重用它。
沒有與底層數據庫的進一步交互,例如顯式調用Connection 的 close()方法。
正如預期的那樣,使用我們的BasicConnectionPool類很簡單。
讓我們創建一個簡單的單元測試并獲得一個池化的內存H2連接:
@Test
public whenCalledgetConnection_thenCorrect() {
ConnectionPool connectionPool = BasicConnectionPool
.create("jdbc:h2:mem:test", "user", "password");
assertTrue(connectionPool.getConnection().isValid(1));
}
當然,還有很多空間可以調整/擴展我們的連接池實現的當前功能。
例如,我們可以重構getConnection()方法并添加對最大池大小的支持。如果所有可用連接都被占用,并且當前池大小小于配置的最大值,則該方法將創建一個新連接。
我們還可以驗證從池中獲得的連接是否仍然存在,然后再將其傳遞給客戶端:
@Override
public Connection getConnection() throws SQLException {
if (connectionPool.isEmpty()) {
if (usedConnections.size() < MAX_POOL_SIZE) {
connectionPool.add(createConnection(url, user, password));
} else {
throw new RuntimeException(
"Maximum pool size reached, no available connections!");
}
}
Connection connection = connectionPool
.remove(connectionPool.size() - 1);
if(!connection.isValid(MAX_TIMEOUT)){
connection = createConnection(url, user, password);
}
usedConnections.add(connection);
return connection;
}
請注意,該方法現在拋出SQLException,這意味著我們還必須更新接口簽名。
或者我們可以添加一個方法來優雅地關閉我們的連接池實例:
public void shutdown() throws SQLException {
usedConnections.forEach(this::releaseConnection);
for (Connection c : connectionPool) {
c.close();
}
connectionPool.clear();
}
在生產就緒的實現中,連接池應該提供一堆額外的功能,例如跟蹤當前正在使用的連接的能力,支持準備好的語句池等等。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習