在前面我們講解了JDBC的基本用法,包括Statement、PreparedStatement、CallableStatement接口的使用,存儲過程、事務的使用方法。但是在我們每次操作數據庫時,都會創建一個新的Connection,並且執行完畢後斷開,Connection的新建、關閉操作十分的耗時並且耗資源,如果頻繁的操作還會影響數據庫服務器性能,過多的連接甚至會打崩數據庫服務器。
SUN說:要有’光’,於是數據庫連接池就誕生了。感謝之前的大神,讓現在的開發者可以站在他們的肩膀上,在我們的項目中可以很方便的使用數據庫連接池來解決上面說到的難題。下面就讓我們一起來看下數據庫連接池的概念和具體用法。
1.數據庫連接池的概念
在JDBC編程中,每次創建和斷開Connection對象都會消耗一定的時間和IO資源。這些問題在我們本地開發時可能還無法直觀的體會到,但是如果項目的部署生產後,併發量達到一定程度時,問題就會暴露出來,而且是災難性的。因此,數據庫連接池就在千呼萬喚中出現了。
數據庫連接池(DataBase Connection Pool)負責分配、管理和釋放數據庫連接,它允許應用程序重複使用現有的數據庫連接,而不是重新建立。這個思想類似於目前比較流行的"共享"模式,當你有需要時,去租(申請)一個資源,在你使用完畢歸還(釋放)後,這些資源並不會被"銷燬",而是被系統回收起來等待下一個用戶申請。
上圖爲一個採用了數據庫連接池操作數據庫的簡單示意圖,從中我們可以看到當應用程序想操作數據庫時,並不是向之前一樣直接創建Connection,而是從數據庫連接池中"申請"一個Connection;使用完畢後,連接池會將此Connection進行回收,並且可以將其交付給其他線程使用。並且連接池還可設置最大連接數,最小活躍數等配置,來減少數據庫服務器的壓力。
2.JDBC之DataSource接口
爲了支持對數據庫連接的管理,JDBC在3.0版本時提供了DataSource(數據源)接口,其作爲首選方法替代DriverManager類負責建立與數據庫的連接。DataSource接口位於javax.sql包中,接下來我們簡單瞭解下。
通過DataSource對象訪問的驅動不會將自身註冊到DriverManager中,DataSource的實現類通常會在JNDI(Java Naming and Directory Interface)註冊。
我們來看下DataSource接口中提供的獲取Connection的方法:
- Connection getConnection()
- Connection getConnection(String username, String password)
其中getConnection()
方法的實現如下(以MySQL驅動爲例),可以看到,無參的getConnection方法是使用DataSource中配置好的默認的用戶。兩個方法本質上是相同的,getConnection(String username, String password)
方法可以指定創建連接使用的用戶名和密碼。
//com.mysql.jdbc.jdbc2.optional.MysqlDataSource
/**
* Creates a new connection using the already configured username and
* password.
*
* @return a connection to the database
*
* @throws SQLException
* if an error occurs
*/
public java.sql.Connection getConnection() throws SQLException {
return getConnection(this.user, this.password);
}
MySQL驅動中實現的DataSource,並沒有實現數據庫連接的池化管理,而是使用直連的方式通過Driver來獲取的。而數據庫連接池的實現主要一些開源組織來提供的,下面我們一起來看下目前主流的數據庫連接池。
3.數據庫連接池簡單對比
現在使用比較多的數據庫連接池DBCP、C3P0、BoneCP、Druid等,我們分別來簡單的介紹下。
- C3p0: 支持JDBC2和JDBC3的標準規範,易於擴展。Hibernate、Spring框架支持。單線程,性能較差,適用於小型系統;
- DBCP:Apache組織下的開源連接池實現,也是Tomcat服務器使用的連接池組件, Jakarta commons-pool對象池機制。單線程,併發量低,性能不好,適用於小型系統;
- BoneCP:高效、免費、開源的Java數據庫連接池實現庫,速度最快,高度可擴展;
- Druid:Java語言中最好的數據庫連接池,Druid還能夠提供強大的監控和擴展功能,是一個可用於大數據實時查詢和分析的高容錯、高性能的開源分佈式系統,尤其是當發生代碼部署、機器故障以及其他產品系統遇到宕機等情況時,Druid仍能夠保持100%正常運行。快速的交互式查詢、高可用、可擴展。
幾個數據庫連接池主要功能對比:
Druid | BoneCP | DBCP | C3P0 | |
---|---|---|---|---|
LRU | 是 | 否 | 是 | 否 |
PSCache | 是 | 是 | 是 | 是 |
PSCache-Oracle-Optimized | 是 | 否 | 否 | 否 |
ExceptionSorter | 是 | 否 | 否 | 否 |
更新維護 | 是 | 否 | 否 | 否 |
LRU(最近最久未使用)是一個性能關鍵指標,如果數據庫連接池遵從LRU,有助於數據庫服務器優化,這是重要的指標。在測試中,Druid、DBCP是遵守LRU的。BoneCP、C3P0則不是。BoneCP在mock環境下性能可能好,但在真實環境中就不好了。
PSCache是數據庫連接池的關鍵指標,即PrepareStatement的查詢結果是否緩存。在Oracle中,類似SELECT NAME FROM USER WHERE ID = ?這樣的SQL,啓用PSCache和不啓用PSCache的性能可能是相差一個數量級的。Proxool是不支持PSCache的數據庫連接池,如果你使用Oracle、SQL Server、DB2、Sybase這樣支持遊標的數據庫,那你就完全不用考慮Proxool。
Oracle 10系列的Driver,如果開啓PSCache,會佔用大量的內存,必須做特別的處理,啓用內部的EnterImplicitCache等方法優化才能夠減少內存的佔用。這個功能只有DruidDataSource有。如果你使用的是Oracle Jdbc,你應該毫不猶豫採用DruidDataSource。
ExceptionSorter是一個很重要的容錯特性,如果一個連接產生了一個不可恢復的錯誤,必須立刻從連接池中去掉,否則會連續產生大量錯誤。這個特性,目前只有Druid實現。Druid的實現參考自JBoss數據庫連接池。
經過對比發現,Druid當之無愧是Java語言中最好的數據庫連接池。不僅提供了高效的數據庫連接池,還提供了強大的監控與擴展功能。
因此,我們下面使用Druid來作爲我們的數據庫連接池,並給出示例配置。
4.Druid的使用
Druid既然是Java語言裏最好的數據庫連接池,並且深受程序員圈的廣泛認可,那我們自然也是一步上壘,直接動手擼代碼。
這裏首先需要下載Druid的jar包(如果是maven項目,可以在pom文件中配置druid的依賴),我們選擇1.1.21這個版本,下載地址:https://mvnrepository.com/artifact/com.alibaba/druid/1.1.21
我們將jar導入到lib目錄中,並build path。然後,在src目錄下新建druid.properties配置文件,目錄結構如下圖所示:
druid.properties中的屬性如下,其中*是必須要配置的,其他屬性可以選擇性配置。
#驅動類所在位置
driverClassName=com.mysql.jdbc.Driver
#數據庫url*
url=jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8
#用戶名*
username=root
#密碼*
password=123456
#最大活躍連接數*
maxActive=20
#初始化時建立物理連接的個數
initialSize=1
#獲取連接最大等待時間,單位ms
maxWait=60000
#最小空閒連接數
minIdle=5
#空閒連接保持最大存活時間,單位ms
minEvictableIdleTimeMillis=300000
然後,我們在util包中新建DruidUtils,在其中創建數據源,並提供獲取Connection的靜態方法,其中的代碼如下:
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class DruidUtils {
private static DataSource dataSource;
private DruidUtils() {
}
// 因爲靜態方法已經初始化了DataSource,這裏直接返回即可
public static DataSource getInstance() {
return dataSource;
}
// 創建數據源,根據配置文件中的屬性初始化數據庫連接池
static {
Properties pro = new Properties();
try {
pro.load(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
// 異常處理
e.printStackTrace();
}
}
/**
* 從數據庫連接池中獲取連接
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
配置好DruidUtil後,其使用和之前的JDBC操作數據庫基本相同,除了獲取連接的方式由從DriverManager變爲從DataSource中獲取。我們簡單的寫個測試代碼:
public void selectById(int id) throws SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = DruidUtils.getConnection();
// 創建Statement對象
statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE id = " + id;
// 獲取查詢結果集
resultSet = statement.executeQuery(sql);
User user = new User();
while (resultSet.next()) {
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
System.out.println(user);
}
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
//此處的釋放資源代碼可參考前幾篇博客
JDBCUtil.release(resultSet, statement, connection);
}
}
我們執行測試代碼selectById(2)
,運行結果如下圖所示:
在上面的的代碼中,我們在finally代碼塊中進行了資源釋放,這裏大家可能會有些疑惑,就是獲取的連接如果關閉掉的話,那下次使用不還是要新建連接了麼?爲了解答疑惑,這裏我們還看下druid中的源碼,
//druid中重寫的getConnection方法,可以看到,其返回值的類型爲DruidPooledConnection
@Override
public DruidPooledConnection getConnection() throws SQLException {
return getConnection(maxWait);
}
從上圖中,我們可以看到DruidPooledConnection類是是繼承了Connection接口的,而且還是支持數據庫數據庫連接池。並且在DruidPooledConnection類中,還重寫了Connection接口中的close方法,源碼如下:
@Override
public void close() throws SQLException {
//如果已經釋放,直接返回
if (this.disable) {
return;
}
//判斷是否被重複close
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
//回收此連接
//...
//設置回收成功標誌位
this.disable = true;
}
因此,我們在使用數據庫連接池時,還可以保持着使用JDBC編程的習慣,但是應用程序處理數據庫操作併發的能力卻悄然的大幅提升。
5.總結
數據庫連接池自其誕生以來,就廣泛的應用於項目程序中,其對性能的提升是毋庸置疑的,並且,JDBC還新增了DataSource接口來支持數據庫連接池,因此,我們需要掌握這把利器,並能使用好它。
並且Spring、SpringBoot等框架對Druid的支持非常好,讓我們可以快速的完成Druid配置,並且Druid還提供的監控的功能,可以滿足你更多的需求。
參考閱讀:
又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。
Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。
有任何疑問,可以評論區留言。