一、為什麼我們要用連接池技術?
前面的數據庫連接的建立及關閉資源的方法有些缺陷。統艙傳統數據庫訪問方式:一次數據庫訪問對應一個物理連接,每次操作數據庫都要打開、關閉該物理連接, 系統性能嚴重受損。
解決方案:數據庫連接池(Connection Pool)。
系統初始運行時,主動建立足夠的連接,組成一個池.每次應用應用程序請求數據庫連接時,無需重新打開連接,而是從池中取出已有的連接,使用完後,不再關閉,而是歸還。
二、連接池主要由三部分組成:連接池的建立、連接池中連接的使用管理、連接池的關閉。
三、連接池技術的核心思想
連接復用,通過建立一個數據庫連接池以及一套連接使用、分配、管理策略,使得該連接池中的連接可以得到高效、安全的復用,避免了數據庫連接頻繁建立、關閉的開銷。
1.連接池的建立
在系統初始化時,根據相應的配置創建連接並放置在連接池中,以便需要使用時能從連接池中獲取,這樣就可以避免連接隨意的建立、關閉造成的開銷。
2.連接池中連接的使用管理
連接池管理策略是連接池機制的核心。當連接池建立後,如何對連接池中的連接進行管理,解決好連接池內連接的分配和釋放,對系統的性能有很大的影響。連接的合理分配、釋放可提高連接的復用,降低了系統建立新連接的開銷,同時也加速了用戶的訪問速度。
采用的方法是一個很有名的設計模式:Reference Counting(引用記數)。該模式在復用資源方面應用的非常廣泛,把該方法運用到對於連接的分配釋放上,為每一個數據庫連接,保留一個引用記數,用來記錄該連接的使用者的個數。
(1)當客戶請求數據庫連接時,首先查看連接池中是否有空閒連接(指當前沒有分配出去的連接)。如果存在空閒連接,則把連接分配給客戶並作相應處理(即標記該連接為正在使用,引用計數加1)。如果沒有空閒連接,則查看當前所開的連接數是不是已經達到maxConn(最大連接數),如果沒達到就重新創建一個連接給請求的客戶;如果達到就按設定的maxWaitTime(最大等待時間)進行等待,如果等待maxWaitTime後仍沒有空閒連接,就拋出無空閒連接的異常給用戶。
(2)當客戶釋放數據庫連接時,先判斷該連接的引用次數是否超過了規定值,如果超過就刪除該連接,並判斷當前連接池內總的連接數是否小於minConn(最小連接數),若小於就將連接池充滿;如果沒超過就將該連接標記為開放狀態,可供再次復用。可以看出正是這套策略保證了數據庫連接的有效復用,避免頻繁地建立、釋放連接所帶來的系統資源開銷。
3.連接池的關閉
當應用程序退出時,應關閉連接池,此時應把在連接池建立時向數據庫申請的連接對象統一歸還給數據庫(即關閉所有數據庫連接),這與連接池的建立正好是一個相反過程。
我們采用DBCP(DataBase connection pool),數據庫連接池。DBCP(是 apache 上的一個 java 連接池項目,也是 tomcat 使用的連接池組件。單獨使用dbcp需要3個包:commons-dbcp.jar,commons-pool.jar,commons-collections.jar由於建立數據庫連接是一個非常耗時耗資源的行為,所以通過連接池預先同數據庫建立一些連接,放在內存中,應用程序需要建立數據庫連接時直接到連接池中申請一個就行,用完後再放回去。
四、連接池的實現
新建一個java工程並導入相應的包。
配置文件:
1 jdbc.driver=com.mysql.jdbc.Driver 2 jdbc.url=jdbc:mysql://localhost:3306/csdn 3 jdbc.user=root 4 jdbc.password=123456 5 initsize=1 6 maxactive=1 7 maxwait=5000 8 maxidle=1 9 minidle=1
dbcp的基本配置的介紹
1.initialSize :連接池啟動時創建的初始化連接數量(默認值為0)
2.maxActive :連接池中可同時連接的最大的連接數(默認值為8,調整為20,高峰單機器在20並發左右,自己根據應用場景定)
3.maxIdle:連接池中最大的空閒的連接數,超過的空閒連接將被釋放,如果設置為負數表示不限制(默認為8個,maxIdle不能設置太小,因為假如在高負載的情況下,連接的打開時間比關閉的時間快,會引起連接池中idle的個數 上升超過maxIdle,而造成頻繁的連接銷毀和創建,類似於jvm參數中的Xmx設置)
4.minIdle:連接池中最小的空閒的連接數,低於這個數量會被創建新的連接(默認為0,調整為5,該參數越接近maxIdle,性能越好,因為連接的創建和銷毀,都是需要消耗資源的;但是不能太大,因為在機器很空閒的時候,也會創建低於minidle個數的連接,類似於jvm參數中的Xmn設置)
5.maxWait :最大等待時間,當沒有可用連接時,連接池等待連接釋放的最大時間,超過該時間限制會拋出異常,如果設置-1表示無限等待(默認為無限,調整為60000ms,避免因線程池不夠用,而導致請求被無限制掛起)
DBUtil源碼如下:
1 package com.daliu.jdbc; 2 3 import java.io.InputStream; 4 import java.sql.Connection; 5 import java.sql.SQLException; 6 import java.util.Properties; 7 8 import org.apache.commons.dbcp.BasicDataSource; 9 10 /** 11 * 使用連接池技術管理數據庫連接 12 */ 13 public class DBUtil { 14 15 //數據庫連接池 16 private static BasicDataSource dbcp; 17 18 //為不同線程管理連接 19 private static ThreadLocal<Connection> tl; 20 21 //通過配置文件來獲取數據庫參數 22 static{ 23 try{ 24 Properties prop 25 = new Properties(); 26 27 InputStream is 28 = DBUtil.class.getClassLoader() 29 .getResourceAsStream( 30 "com/daliu/jdbc/db.properties"); 31 32 prop.load(is); 33 is.close(); 34 35 //一、初始化連接池 36 dbcp = new BasicDataSource(); 37 38 39 //設置驅動 (Class.forName()) 40 dbcp.setDriverClassName(prop.getProperty("jdbc.driver")); 41 //設置url 42 dbcp.setUrl(prop.getProperty("jdbc.url")); 43 //設置數據庫用戶名 44 dbcp.setUsername(prop.getProperty("jdbc.user")); 45 //設置數據庫密碼 46 dbcp.setPassword(prop.getProperty("jdbc.password")); 47 //初始連接數量 48 dbcp.setInitialSize( 49 Integer.parseInt( 50 prop.getProperty("initsize") 51 ) 52 ); 53 //連接池允許的最大連接數 54 dbcp.setMaxActive( 55 Integer.parseInt( 56 prop.getProperty("maxactive") 57 ) 58 ); 59 //設置最大等待時間 60 dbcp.setMaxWait( 61 Integer.parseInt( 62 prop.getProperty("maxwait") 63 ) 64 ); 65 //設置最小空閒數 66 dbcp.setMinIdle( 67 Integer.parseInt( 68 prop.getProperty("minidle") 69 ) 70 ); 71 //設置最大空閒數 72 dbcp.setMaxIdle( 73 Integer.parseInt( 74 prop.getProperty("maxidle") 75 ) 76 ); 77 //初始化線程本地 78 tl = new ThreadLocal<Connection>(); 79 }catch(Exception e){ 80 e.printStackTrace(); 81 } 82 } 83 84 /** 85 * 獲取數據庫連接 86 * @return 87 * @throws SQLException 88 */ 89 public static Connection getConnection() throws SQLException{ 90 /* 91 * 通過連接池獲取一個空閒連接 92 */ 93 Connection conn 94 = dbcp.getConnection(); 95 tl.set(conn); 96 return conn; 97 } 98 99 100 /** 101 * 關閉數據庫連接 102 */ 103 public static void closeConnection(){ 104 try{ 105 Connection conn = tl.get(); 106 if(conn != null){ 107 /* 108 * 通過連接池獲取的Connection 109 * 的close()方法實際上並沒有將 110 * 連接關閉,而是將該鏈接歸還。 111 */ 112 conn.close(); 113 tl.remove(); 114 } 115 }catch(Exception e){ 116 e.printStackTrace(); 117 } 118 } 119 120 /** 121 * 測試是否連接成功 122 * @param args 123 * @throws SQLException 124 */ 125 public static void main(String[] args) throws SQLException { 126 System.out.println(getConnection()); 127 } 128 }
效果如下: