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