一锯茄、關(guān)于數(shù)據(jù)庫連接池
一個普通的java程序,要查詢數(shù)據(jù)庫的數(shù)據(jù)茶没,基本流程是這樣的:
可以看到肌幽,進行一次查詢,要進行很多次網(wǎng)絡(luò)交互抓半,這樣的缺點是:
- 網(wǎng)絡(luò)IO多喂急;
- 響應(yīng)時間長,導致QPS降低笛求;
- 頻繁創(chuàng)建連接和關(guān)閉連接廊移,浪費數(shù)據(jù)庫資源,影響服務(wù)器性能涣易。
什么是數(shù)據(jù)庫連接池画机?
顧名思義,就是一個池子新症,里面放著數(shù)據(jù)庫連接步氏,應(yīng)用服務(wù)需要的時候就去池子里面拿,用完之后歸還給池子徒爹。數(shù)據(jù)庫連接池負責分配荚醒、管理、釋放數(shù)據(jù)庫連接隆嗅,它允許應(yīng)用服務(wù)重復使用數(shù)據(jù)庫連接界阁,而非重新建立。
使用連接池之后胖喳,流程是這樣的:
數(shù)據(jù)庫的連接創(chuàng)建和關(guān)閉連接均由連接池來實現(xiàn)。這樣做的優(yōu)點有:
- 減少網(wǎng)絡(luò)開銷丽焊;
- 提升數(shù)據(jù)庫性能较剃;
那數(shù)據(jù)庫連接池是如何管理池子中的數(shù)據(jù)庫連接的呢?
- 應(yīng)用啟動時技健,根據(jù)配置的最小連接數(shù)写穴,在連接池將創(chuàng)建此數(shù)目的數(shù)據(jù)庫連接放到池中。
- 應(yīng)用訪問時雌贱,首先查看連接池中是否有空閑連接啊送,如果存在空閑連接偿短,則將連接分配給客戶使用;如果沒有空閑連接馋没,則查看當前所開的連接數(shù)是否已經(jīng)達到最大連接數(shù)昔逗,如果沒達到就重新創(chuàng)建一個連接給請求的客戶;如果達到就按設(shè)定的最大等待時間進行等待篷朵,如果超出最大等待時間纤子,則拋出異常給客戶。 當客戶釋放數(shù)據(jù)庫連接時款票,先判斷該連接的引用次數(shù)是否超過了規(guī)定值控硼,如果超過就從連接池中刪除該連接,否則保留等待再次使用艾少。
- 應(yīng)用關(guān)閉時卡乾,關(guān)閉池中所有連接,釋放所有資源缚够。
此過程會涉及的配置:最小連接數(shù)幔妨,最大連接數(shù),最長等待時間谍椅,均配置在應(yīng)用服務(wù)配置文件中误堡。
二、DataSource的實現(xiàn)
現(xiàn)在很多WEB服務(wù)器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現(xiàn)雏吭,即連接池的實現(xiàn)锁施。通常我們把DataSource的實現(xiàn),按其英文含義稱之為數(shù)據(jù)源杖们,數(shù)據(jù)源中都包含了數(shù)據(jù)庫連接池的實現(xiàn)悉抵。也有一些開源組織提供了數(shù)據(jù)源的獨立實現(xiàn):
- DBCP 數(shù)據(jù)庫連接池
- C3P0 數(shù)據(jù)庫連接池
在使用了數(shù)據(jù)庫連接池之后,在項目的實際開發(fā)中就不需要編寫連接數(shù)據(jù)庫的代碼了摘完,直接從數(shù)據(jù)源獲得數(shù)據(jù)庫的連接姥饰。
DBCP數(shù)據(jù)源
DBCP 是 Apache 軟件基金組織下的開源連接池實現(xiàn),要使用DBCP數(shù)據(jù)源孝治,需要應(yīng)用程序應(yīng)在系統(tǒng)中增加如下兩個 jar 文件:
- Commons-dbcp.jar:連接池的實現(xiàn)
- Commons-pool.jar:連接池實現(xiàn)的依賴庫
Tomcat 的連接池正是采用該連接池來實現(xiàn)的列粪。該數(shù)據(jù)庫連接池既可以與應(yīng)用服務(wù)器整合使用,也可由應(yīng)用程序獨立使用谈飒。
在應(yīng)用程序中加入dbcp連接池
- 導入相關(guān)jar包commons-dbcp-1.2.2.jar岂座、commons-pool.jar
- 在類目錄下加入dbcp的配置文件:dbcpconfig.properties
dbcpconfig.properties的配置信息如下:
#連接設(shè)置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy
username=root
password=XDP
#<!-- 初始化連接 -->
initialSize=10
#最大連接數(shù)量
maxActive=50
#<!-- 最大空閑連接 -->
maxIdle=20
#<!-- 最小空閑連接 -->
minIdle=5
#<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驅(qū)動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們步绸。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由連接池所創(chuàng)建的連接的自動提交(auto-commit)狀態(tài)掺逼。
defaultAutoCommit=true
#driver default 指定由連接池所創(chuàng)建的連接的只讀(read-only)狀態(tài)吃媒。
#如果沒有設(shè)置該值瓤介,則“setReadOnly”方法將不被調(diào)用吕喘。(某些驅(qū)動并不支持只讀模式,如:Informix)
defaultReadOnly=
#driver default 指定由連接池所創(chuàng)建的連接的事務(wù)級別(TransactionIsolation)刑桑。
#可用值為下列之一:(詳情可見javadoc氯质。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
在獲取數(shù)據(jù)庫連接的工具類的靜態(tài)代碼塊中創(chuàng)建池
//數(shù)據(jù)庫連接工具類
public class JdbcDBCP {
/**
* 在java中,編寫數(shù)據(jù)庫連接池需實現(xiàn)java.sql.DataSource接口祠斧,每一種數(shù)據(jù)庫連接池都是DataSource接口的實現(xiàn)
* DBCP連接池就是java.sql.DataSource接口的一個具體實現(xiàn)
*/
private static DataSource ds = null;
//在靜態(tài)代碼塊中創(chuàng)建數(shù)據(jù)庫連接池
static{
try{
//加載dbcpconfig.properties配置文件
InputStream in = JdbcDBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
//創(chuàng)建數(shù)據(jù)源
ds = BasicDataSourceFactory.createDataSource(prop);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
//從數(shù)據(jù)源中獲取數(shù)據(jù)庫連接
public static Connection getConnection() throws SQLException{
//從數(shù)據(jù)源中獲取數(shù)據(jù)庫連接
return ds.getConnection();
}
/**
* 釋放資源闻察,釋放的資源包括Connection數(shù)據(jù)庫連接對象,負責執(zhí)行SQL命
*令的Statement對象琢锋,存儲查詢結(jié)果的ResultSet對象
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//關(guān)閉存儲查詢結(jié)果的ResultSet對象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//關(guān)閉負責執(zhí)行SQL命令的Statement對象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//將Connection連接對象還給數(shù)據(jù)庫連接池
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
三辕漂、配置Tomcat數(shù)據(jù)源
在實際開發(fā)中,我們有時候還會使用服務(wù)器提供給我們的數(shù)據(jù)庫連接池吴超,比如我們希望Tomcat服務(wù)器在啟動的時候可以幫我們創(chuàng)建一個數(shù)據(jù)庫連接池钉嘹,那么在應(yīng)用程序中就不需要手動去創(chuàng)建數(shù)據(jù)庫連接池,直接使用Tomcat服務(wù)器創(chuàng)建好的數(shù)據(jù)庫連接池即可鲸阻。要想讓Tomcat服務(wù)器在啟動的時候創(chuàng)建一個數(shù)據(jù)庫連接池跋涣,那么需要簡單配置一下Tomcat服務(wù)器。
JNDI技術(shù)簡介
JNDI(Java Naming and Directory Interface)鸟悴,Java命名和目錄接口陈辱,它對應(yīng)于J2SE中的javax.naming包。這套API的主要作用在于:它可以把Java對象放在一個容器中(JNDI容器)细诸,并為容器中的java對象取一個名稱沛贪,以后程序想獲得Java對象,只需通過名稱檢索即可震贵。其核心API為Context鹏浅,它代表JNDI容器,其lookup方法為檢索容器中對應(yīng)名稱的對象屏歹。
Tomcat服務(wù)器創(chuàng)建的數(shù)據(jù)源是以JNDI資源的形式發(fā)布的隐砸,所以說在Tomat服務(wù)器中配置一個數(shù)據(jù)源實際上就是在配置一個JNDI資源,通過查看Tomcat文檔蝙眶,我們知道使用如下的方式配置tomcat服務(wù)器的數(shù)據(jù)源:
<Context>
<Resource name="jdbc/datasource" auth="Container"
type="javax.sql.DataSource" username="root" password="XDP"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbcstudy"
maxActive="8" maxIdle="4"/>
</Context>
服務(wù)器創(chuàng)建好數(shù)據(jù)源之后季希,我們的應(yīng)用程序又該怎么樣得到這個數(shù)據(jù)源呢?Tomcat服務(wù)器創(chuàng)建好數(shù)據(jù)源之后是以JNDI的形式綁定到一個JNDI容器中的幽纷,可以把JNDI想象成一個大大的容器式塌,我們可以往這個容器中存放一些對象、一些資源友浸,JNDI容器中存放的對象和資源都會有一個獨一無二的名稱峰尝。應(yīng)用程序想從JNDI容器中獲取資源時,只需要告訴JNDI容器要獲取的資源的名稱收恢,JNDI根據(jù)名稱去找到對應(yīng)的資源后返回給應(yīng)用程序武学。我們平時做javaEE開發(fā)時祭往,服務(wù)器會為我們的應(yīng)用程序創(chuàng)建很多資源,比如request對象火窒,response對象硼补,服務(wù)器創(chuàng)建的這些資源有兩種方式提供給我們的應(yīng)用程序使用:
- 第一種是通過方法參數(shù)的形式傳遞進來,比如我們在Servlet中寫的doPost和doGet方法中使用到的request對象和response對象就是服務(wù)器以參數(shù)的形式傳遞給我們的熏矿。
- 第二種就是JNDI的方式已骇,服務(wù)器把創(chuàng)建好的資源綁定到JNDI容器中去,應(yīng)用程序想要使用資源時票编,就直接從JNDI容器中獲取相應(yīng)的資源即可褪储。
對于上面的name="jdbc/datasource"數(shù)據(jù)源資源,在應(yīng)用程序中可以用如下的代碼去獲然塾颉:
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此種配置下乱豆,數(shù)據(jù)庫的驅(qū)動jar文件需放置在tomcat的lib下。
配置Tomcat數(shù)據(jù)源
- 在Web項目的WebRoot目錄下的META-INF目錄創(chuàng)建一個context.xml文件
- 在context.xml文件配置tomcat服務(wù)器的數(shù)據(jù)源
<Context>
<Resource
name="jdbc/datasource"
auth="Container"
type="javax.sql.DataSource"
username="root"
password="XDP"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/jdbcstudy"
maxActive="8"
maxIdle="4"/>
</Context>
- 將數(shù)據(jù)庫的驅(qū)動jar文件需放置在tomcat的lib下
- 在獲取數(shù)據(jù)庫連接的工具類的靜態(tài)代碼塊中獲取JNDI容器中的數(shù)據(jù)源
//數(shù)據(jù)庫連接工具類
public class JdbcJNDI {
private static DataSource ds = null;
//在靜態(tài)代碼塊中創(chuàng)建數(shù)據(jù)庫連接池
static{
try{
//初始化JNDI
Context initCtx = new InitialContext();
//得到JNDI容器
Context envCtx = (Context) initCtx.lookup("java:comp/env");
//從JNDI容器中檢索name為jdbc/datasource的數(shù)據(jù)源
ds = (DataSource)envCtx.lookup("jdbc/datasource");
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
//從數(shù)據(jù)源中獲取數(shù)據(jù)庫連接
public static Connection getConnection() throws SQLException{
//從數(shù)據(jù)源中獲取數(shù)據(jù)庫連接
return ds.getConnection();
}
/**
* 釋放資源吊趾,
* 釋放的資源包括Connection數(shù)據(jù)庫連接對象宛裕,負責執(zhí)行SQL命令的Statement對象,存儲查詢結(jié)果的ResultSet對象
*/
public static void release(Connection conn,Statement st,ResultSet rs){
if(rs!=null){
try{
//關(guān)閉存儲查詢結(jié)果的ResultSet對象
rs.close();
}catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st!=null){
try{
//關(guān)閉負責執(zhí)行SQL命令的Statement對象
st.close();
}catch (Exception e) {
e.printStackTrace();
}
}
if(conn!=null){
try{
//將Connection連接對象還給數(shù)據(jù)庫連接池
conn.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
四论泛、Java常見數(shù)據(jù)庫連接池性能比較
目前揩尸,流行的Java數(shù)據(jù)庫連接池有HikariCP,druid屁奏,tomcat-jdbc岩榆,dbcp,c3p0坟瓢。性能從高到低順序排列(單從性能角度看)勇边。下圖是HikariCP官網(wǎng)給出的性能對比:
HikariCP是目前最快的Java數(shù)據(jù)庫連接池,spring boot 2.x已經(jīng)使用HikariCP作為默認的數(shù)據(jù)庫連接池折联,足見其優(yōu)秀粒褒。
五、HikariCP的優(yōu)越性
HikariCP為什么這么快呢?
- 優(yōu)化并精簡字節(jié)碼:使用Java字節(jié)碼修改類庫Javassist來生成委托實現(xiàn)動態(tài)代理诚镰,JDK Proxy生成的字節(jié)碼更少奕坟。
- 使用更好的并發(fā)集合類ConcurrentBag。
- 使用FastList替代ArrayList清笨。