java中数据库连接池_Java中的数据库连接池
本文譯自Baeldung的博客
概述
連接池是一種眾所周知的數據庫訪問模式,主要目的是減少創建數據庫連接和讀/寫數據庫操作的開銷。
簡單來說,連接池本質上就是數據庫連接緩存的一種實現方式,可以通過對其進行配置來滿足特定的需求。
本文中,我們會簡要介紹一些流行的連接池框架,之后也會討論如何從零開始實現一個連接池。
為何使用連接池
關于這個問題,只要我們分析一下典型的數據庫連接的生命周期中所涉及的步驟,就會明白為什么:
使用數據庫驅動建立一個到數據庫連接;
建立 TCP socket用于讀/寫數據;
通過socket來讀寫數據;
關閉連接;
關閉socket;
很顯然,數據庫連接是非常昂貴的操作,因此在每個可能的應用場景中都要盡量將數據庫連接操作降到最低。
這也就是數據庫連接池發揮作用的地方。
只需要簡單地實現一個數據庫連接容器,使我們可以復用一些已存在的數據庫連接,我們就可以有效地節省大量昂貴的數據庫連接操作消耗的時間成本,從而提高數據庫驅動應用程序的整體性能。
JDBC連接池框架
從實用角度來看,考慮到目前已有很多企業級連接池框架,從頭開始實行連接池是沒有意義的。但是從學習角度,也就是本文的角度來看,并不是無意義的。
即便如此,在開始學習如何實現基本的連接池之前,讓我們首先了解幾個流行的連接池框架。
Apache Commons DBCP
我們首先看一下Apache Commons DBCP組件,這是一個功能齊全的連接池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的屬性。使用DBCPDateSource類獲取池化連接的方式如下:
Connection?con?=?DBCPDataSource.getConnection();
復制代碼
C3P0
接著介紹的是C3P0,由Steve Waldman開發,是一個強大的JDBC4連接和語句池框架。
public?class?C3poDataSource{
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?C3poDataSource(){}
}
復制代碼
通過C3PoDataSource類獲取池化連接的方式與前面類似:
Connection?con?=?C3poDataSource.getConnection();
復制代碼
HikariCP
最后來看一下HikariCP,一個由Breet Wooldridge開發的快速JDBC連接池框架。我們會在后續的文章中詳細介紹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(){}
}
復制代碼
通過HikariCPDataSource類獲取池化連接的方式同樣很簡單:
Connection?con?=?HikariCPDataSource.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?connectionPool;
private?List?usedConnections?=?new?ArrayList<>();
private?static?int?INITIAL_POOL_SIZE?=?10;
public?static?BasicConnectionPool?create(String?url,
String?user,
String?password)
throws?SQLException{
List?pool?=?new?ArrayList<>(INITIAL_POOL_SIZE);
for?(int?i?=?0;?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實現類,此時最好將該方法放在接口中,從而獲得更靈活的設計和更強的內聚性。
需要強調的一點是,一旦創建了連接池,所有的連接都會從池中獲取,因此不需要創建新的連接。此外,當一個連接被釋放時,它實際上是被歸還到池中,以便其他客戶端可以重用它。
這里與底層數據庫沒有任何進一步的交互,例如對連接的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()?
connectionPool.add(createConnection(url,?user,?password));
}?else?{
throw?new?RuntimeException(
"Maximum?pool?size?reached,?no?available?connections!");
}
}
Connection?connection?=?connectionPool.remove(connectionPool.size()?-?1);
usedConnections.add(connection);
return?connection;
}
復制代碼
需要注意,該方法在這里拋出了SQLException,這意味著我們也需要修改接口中的方法簽名。
此外,我們也可以增加方法來優雅地關閉連接池實例:
public?void?shutdown()?throws?SQLException{
usedConnections.forEach(this::releaseConnection);
for?(Connection?c?:?connectionPool)?{
c.close();
}
connectionPool.clear();
}
復制代碼
在企業級實現中,連接池需要提供很多額外的特性,比如跟蹤當前使用中的連接的能力,對于預編譯語句池的支持,等等。
為了保證簡潔明了,我們省略了這些額外特性的實現,同時提供的也是非線程安全的實現。
在本文中,我們研究了什么是連接池,并學習了如何實現我們自己的連接池。
當然,我們需要在應用程序中添加連接池時,不必從頭開發全新的連接池。這就是為什么我們首先對線程池做了簡單的介紹,并展示了一些流行的連接池框架,以便于我們可以清楚地了解它們的使用方式,并選擇最適合我們要求的框架。
總結
以上是生活随笔為你收集整理的java中数据库连接池_Java中的数据库连接池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java web ftp上传_java
- 下一篇: java bs架构书_基于BS架构的图书