今天工做中遇到新的需求,要將項目DBCP鏈接切換到本身的ZDAL,因而花了點時間研究數據庫鏈接池。html
項目Github地址:java
轉自:http://www.cnblogs.com/xdp-gacl/p/4002804.htmlmysql
-------sql
用戶每次請求都須要向數據庫得到連接,而數據庫建立鏈接一般須要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,數據庫服務器就須要建立10萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機。以下圖所示:數據庫
數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出.對數據庫鏈接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標.數據庫鏈接池正式針對這個問題提出來的.數據庫鏈接池負責分配,管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。以下圖所示:apache
數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中, 這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的.不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量.鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中.編程
數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:服務器
編寫鏈接池需實現java.sql.DataSource接口。DataSource接口中定義了兩個重載的getConnection方法:oracle
實現DataSource接口,並實現鏈接池功能的步驟:app
數據庫鏈接池核心代碼
使用動態代理技術構建鏈接池中的connection
1 proxyConn = (Connection) Proxy.newProxyInstance(this.getClass() 2 .getClassLoader(), conn.getClass().getInterfaces(), 3 new InvocationHandler() { 4 //此處爲內部類,當close方法被調用時將conn還回池中,其它方法直接執行 5 public Object invoke(Object proxy, Method method, 6 Object[] args) throws Throwable { 7 if (method.getName().equals("close")) { 8 pool.addLast(conn); 9 return null; 10 } 11 return method.invoke(conn, args); 12 } 13 });
數據庫鏈接池編寫範例:
1 package me.gacl.demo; 2 3 import java.io.InputStream; 4 import java.io.PrintWriter; 5 import java.lang.reflect.InvocationHandler; 6 import java.lang.reflect.Method; 7 import java.lang.reflect.Proxy; 8 import java.sql.Connection; 9 import java.sql.DriverManager; 10 import java.sql.SQLException; 11 import java.util.LinkedList; 12 import java.util.Properties; 13 import javax.sql.DataSource; 14 15 /** 16 * @ClassName: JdbcPool 17 * @Description:編寫數據庫鏈接池 18 * @author: 孤傲蒼狼 19 * @date: 2014-9-30 下午11:07:23 20 * 21 */ 22 public class JdbcPool implements DataSource{ 23 24 /** 25 * @Field: listConnections 26 * 使用LinkedList集合來存放數據庫連接, 27 * 因爲要頻繁讀寫List集合,因此這裏使用LinkedList存儲數據庫鏈接比較合適 28 */ 29 private static LinkedList<Connection> listConnections = new LinkedList<Connection>(); 30 31 static{ 32 //在靜態代碼塊中加載db.properties數據庫配置文件 33 InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties"); 34 Properties prop = new Properties(); 35 try { 36 prop.load(in); 37 String driver = prop.getProperty("driver"); 38 String url = prop.getProperty("url"); 39 String username = prop.getProperty("username"); 40 String password = prop.getProperty("password"); 41 //數據庫鏈接池的初始化鏈接數大小 42 int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize")); 43 //加載數據庫驅動 44 Class.forName(driver); 45 for (int i = 0; i < jdbcPoolInitSize; i++) { 46 Connection conn = DriverManager.getConnection(url, username, password); 47 System.out.println("獲取到了連接" + conn); 48 //將獲取到的數據庫鏈接加入到listConnections集合中,listConnections集合此時就是一個存放了數據庫鏈接的鏈接池 49 listConnections.add(conn); 50 } 51 52 } catch (Exception e) { 53 throw new ExceptionInInitializerError(e); 54 } 55 } 56 57 @Override 58 public PrintWriter getLogWriter() throws SQLException { 59 // TODO Auto-generated method stub 60 return null; 61 } 62 63 @Override 64 public void setLogWriter(PrintWriter out) throws SQLException { 65 // TODO Auto-generated method stub 66 67 } 68 69 @Override 70 public void setLoginTimeout(int seconds) throws SQLException { 71 // TODO Auto-generated method stub 72 73 } 74 75 @Override 76 public int getLoginTimeout() throws SQLException { 77 // TODO Auto-generated method stub 78 return 0; 79 } 80 81 @Override 82 public <T> T unwrap(Class<T> iface) throws SQLException { 83 // TODO Auto-generated method stub 84 return null; 85 } 86 87 @Override 88 public boolean isWrapperFor(Class<?> iface) throws SQLException { 89 // TODO Auto-generated method stub 90 return false; 91 } 92 93 /* 獲取數據庫鏈接 94 * @see javax.sql.DataSource#getConnection() 95 */ 96 @Override 97 public Connection getConnection() throws SQLException { 98 //若是數據庫鏈接池中的鏈接對象的個數大於0 99 if (listConnections.size()>0) { 100 //從listConnections集合中獲取一個數據庫鏈接 101 final Connection conn = listConnections.removeFirst(); 102 System.out.println("listConnections數據庫鏈接池大小是" + listConnections.size()); 103 //返回Connection對象的代理對象 104 return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){ 105 @Override 106 public Object invoke(Object proxy, Method method, Object[] args) 107 throws Throwable { 108 if(!method.getName().equals("close")){ 109 return method.invoke(conn, args); 110 }else{ 111 //若是調用的是Connection對象的close方法,就把conn還給數據庫鏈接池 112 listConnections.add(conn); 113 System.out.println(conn + "被還給listConnections數據庫鏈接池了!!"); 114 System.out.println("listConnections數據庫鏈接池大小爲" + listConnections.size()); 115 return null; 116 } 117 } 118 }); 119 }else { 120 throw new RuntimeException("對不起,數據庫忙"); 121 } 122 } 123 124 @Override 125 public Connection getConnection(String username, String password) 126 throws SQLException { 127 return null; 128 } 129 }
db.properties配置文件以下:
1 driver=com.mysql.jdbc.Driver 2 url=jdbc:mysql://localhost:3306/jdbcStudy 3 username=root 4 password=XDP 5 6 jdbcPoolInitSize=10
寫一個JdbcUtil測試數據庫鏈接池
1 package me.gacl.utils; 2 3 import java.sql.Connection; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 import me.gacl.demo.JdbcPool; 8 9 public class JdbcUtil { 10 11 /** 12 * @Field: pool 13 * 數據庫鏈接池 14 */ 15 private static JdbcPool pool = new JdbcPool(); 16 17 /** 18 * @Method: getConnection 19 * @Description: 從數據庫鏈接池中獲取數據庫鏈接對象 20 * @Anthor:孤傲蒼狼 21 * @return Connection數據庫鏈接對象 22 * @throws SQLException 23 */ 24 public static Connection getConnection() throws SQLException{ 25 return pool.getConnection(); 26 } 27 28 /** 29 * @Method: release 30 * @Description: 釋放資源, 31 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 32 * @Anthor:孤傲蒼狼 33 * 34 * @param conn 35 * @param st 36 * @param rs 37 */ 38 public static void release(Connection conn,Statement st,ResultSet rs){ 39 if(rs!=null){ 40 try{ 41 //關閉存儲查詢結果的ResultSet對象 42 rs.close(); 43 }catch (Exception e) { 44 e.printStackTrace(); 45 } 46 rs = null; 47 } 48 if(st!=null){ 49 try{ 50 //關閉負責執行SQL命令的Statement對象 51 st.close(); 52 }catch (Exception e) { 53 e.printStackTrace(); 54 } 55 } 56 57 if(conn!=null){ 58 try{ 59 //關閉Connection數據庫鏈接對象 60 conn.close(); 61 }catch (Exception e) { 62 e.printStackTrace(); 63 } 64 } 65 } 66 }
如今不少WEB服務器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即鏈接池的實現。一般咱們把DataSource的實現,按其英文含義稱之爲數據源,數據源中都包含了數據庫鏈接池的實現。
也有一些開源組織提供了數據源的獨立實現:
在使用了數據庫鏈接池以後,在項目的實際開發中就不須要編寫鏈接數據庫的代碼了,直接從數據源得到數據庫的鏈接。
DBCP 是 Apache 軟件基金組織下的開源鏈接池實現,要使用DBCP數據源,須要應用程序應在系統中增長以下兩個 jar 文件:
Tomcat 的鏈接池正是採用該鏈接池來實現的。該數據庫鏈接池既能夠與應用服務器整合使用,也可由應用程序獨立使用。
1.導入相關jar包
commons-dbcp-1.2.2.jar、commons-pool.jar
二、在類目錄下加入dbcp的配置文件:dbcpconfig.properties
dbcpconfig.properties的配置信息以下:
1 #鏈接設置 2 driverClassName=com.mysql.jdbc.Driver 3 url=jdbc:mysql://localhost:3306/jdbcstudy 4 username=root 5 password=XDP 6 7 #<!-- 初始化鏈接 --> 8 initialSize=10 9 10 #最大鏈接數量 11 maxActive=50 12 13 #<!-- 最大空閒鏈接 --> 14 maxIdle=20 15 16 #<!-- 最小空閒鏈接 --> 17 minIdle=5 18 19 #<!-- 超時等待時間以毫秒爲單位 6000毫秒/1000等於60秒 --> 20 maxWait=60000 21 22 23 #JDBC驅動創建鏈接時附帶的鏈接屬性屬性的格式必須爲這樣:[屬性名=property;] 24 #注意:"user" 與 "password" 兩個屬性會被明確地傳遞,所以這裏不須要包含他們。 25 connectionProperties=useUnicode=true;characterEncoding=UTF8 26 27 #指定由鏈接池所建立的鏈接的自動提交(auto-commit)狀態。 28 defaultAutoCommit=true 29 30 #driver default 指定由鏈接池所建立的鏈接的只讀(read-only)狀態。 31 #若是沒有設置該值,則「setReadOnly」方法將不被調用。(某些驅動並不支持只讀模式,如:Informix) 32 defaultReadOnly= 33 34 #driver default 指定由鏈接池所建立的鏈接的事務級別(TransactionIsolation)。 35 #可用值爲下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE 36 defaultTransactionIsolation=READ_UNCOMMITTED
以下圖所示:
三、在獲取數據庫鏈接的工具類(如jdbcUtils)的靜態代碼塊中建立池
1 package me.gacl.util; 2 3 import java.io.InputStream; 4 import java.sql.Connection; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 import java.util.Properties; 9 import javax.sql.DataSource; 10 import org.apache.commons.dbcp.BasicDataSourceFactory; 11 12 /** 13 * @ClassName: JdbcUtils_DBCP 14 * @Description: 數據庫鏈接工具類 15 * @author: 孤傲蒼狼 16 * @date: 2014-10-4 下午6:04:36 17 * 18 */ 19 public class JdbcUtils_DBCP { 20 /** 21 * 在java中,編寫數據庫鏈接池需實現java.sql.DataSource接口,每一種數據庫鏈接池都是DataSource接口的實現 22 * DBCP鏈接池就是java.sql.DataSource接口的一個具體實現 23 */ 24 private static DataSource ds = null; 25 //在靜態代碼塊中建立數據庫鏈接池 26 static{ 27 try{ 28 //加載dbcpconfig.properties配置文件 29 InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); 30 Properties prop = new Properties(); 31 prop.load(in); 32 //建立數據源 33 ds = BasicDataSourceFactory.createDataSource(prop); 34 }catch (Exception e) { 35 throw new ExceptionInInitializerError(e); 36 } 37 } 38 39 /** 40 * @Method: getConnection 41 * @Description: 從數據源中獲取數據庫鏈接 42 * @Anthor:孤傲蒼狼 43 * @return Connection 44 * @throws SQLException 45 */ 46 public static Connection getConnection() throws SQLException{ 47 //從數據源中獲取數據庫鏈接 48 return ds.getConnection(); 49 } 50 51 /** 52 * @Method: release 53 * @Description: 釋放資源, 54 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 55 * @Anthor:孤傲蒼狼 56 * 57 * @param conn 58 * @param st 59 * @param rs 60 */ 61 public static void release(Connection conn,Statement st,ResultSet rs){ 62 if(rs!=null){ 63 try{ 64 //關閉存儲查詢結果的ResultSet對象 65 rs.close(); 66 }catch (Exception e) { 67 e.printStackTrace(); 68 } 69 rs = null; 70 } 71 if(st!=null){ 72 try{ 73 //關閉負責執行SQL命令的Statement對象 74 st.close(); 75 }catch (Exception e) { 76 e.printStackTrace(); 77 } 78 } 79 80 if(conn!=null){ 81 try{ 82 //將Connection鏈接對象還給數據庫鏈接池 83 conn.close(); 84 }catch (Exception e) { 85 e.printStackTrace(); 86 } 87 } 88 } 89 }
測試DBCP數據源
1 package me.gacl.test; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import org.junit.Test; 7 import me.gacl.util.JdbcUtils_DBCP; 8 9 public class DataSourceTest { 10 11 @Test 12 public void dbcpDataSourceTest() { 13 Connection conn = null; 14 PreparedStatement st = null; 15 ResultSet rs = null; 16 try{ 17 //獲取數據庫鏈接 18 conn = JdbcUtils_DBCP.getConnection(); 19 String sql = "insert into test1(name) values(?)"; 20 st = conn.prepareStatement(sql); 21 st.setString(1, "gacl"); 22 st.executeUpdate(); 23 //獲取數據庫自動生成的主鍵 24 rs = st.getGeneratedKeys(); 25 if(rs.next()){ 26 System.out.println(rs.getInt(1)); 27 } 28 }catch (Exception e) { 29 e.printStackTrace(); 30 }finally{ 31 //釋放資源 32 JdbcUtils_DBCP.release(conn, st, rs); 33 } 34 } 35 }
C3P0是一個開源的JDBC鏈接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。C3P0數據源在項目開發中使用得比較多。
c3p0與dbcp區別
1.導入相關jar包
c3p0-0.9.2-pre1.jar、mchange-commons-0.2.jar,若是操做的是Oracle數據庫,那麼還須要導入c3p0-oracle-thin-extras-0.9.2-pre1.jar
二、在類目錄下加入C3P0的配置文件:c3p0-config.xml
c3p0-config.xml的配置信息以下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- 3 c3p0-config.xml必須位於類路徑下面 4 private static ComboPooledDataSource ds; 5 static{ 6 try { 7 ds = new ComboPooledDataSource("MySQL"); 8 } catch (Exception e) { 9 throw new ExceptionInInitializerError(e); 10 } 11 } 12 --> 13 14 <c3p0-config> 15 <!-- 16 C3P0的缺省(默認)配置, 17 若是在代碼中「ComboPooledDataSource ds = new ComboPooledDataSource();」這樣寫就表示使用的是C3P0的缺省(默認)配置信息來建立數據源 18 --> 19 <default-config> 20 <property name="driverClass">com.mysql.jdbc.Driver</property> 21 <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> 22 <property name="user">root</property> 23 <property name="password">XDP</property> 24 25 <property name="acquireIncrement">5</property> 26 <property name="initialPoolSize">10</property> 27 <property name="minPoolSize">5</property> 28 <property name="maxPoolSize">20</property> 29 </default-config> 30 31 <!-- 32 C3P0的命名配置, 33 若是在代碼中「ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");」這樣寫就表示使用的是name是MySQL的配置信息來建立數據源 34 --> 35 <named-config name="MySQL"> 36 <property name="driverClass">com.mysql.jdbc.Driver</property> 37 <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> 38 <property name="user">root</property> 39 <property name="password">XDP</property> 40 41 <property name="acquireIncrement">5</property> 42 <property name="initialPoolSize">10</property> 43 <property name="minPoolSize">5</property> 44 <property name="maxPoolSize">20</property> 45 </named-config> 46 47 </c3p0-config>
以下圖所示:
三、在獲取數據庫鏈接的工具類(如jdbcUtils)的靜態代碼塊中建立池
1 package me.gacl.util; 2 3 import java.sql.Connection; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 import com.mchange.v2.c3p0.ComboPooledDataSource; 8 9 /** 10 * @ClassName: JdbcUtils_C3P0 11 * @Description: 數據庫鏈接工具類 12 * @author: 孤傲蒼狼 13 * @date: 2014-10-4 下午6:04:36 14 * 15 */ 16 public class JdbcUtils_C3P0 { 17 18 private static ComboPooledDataSource ds = null; 19 //在靜態代碼塊中建立數據庫鏈接池 20 static{ 21 try{ 22 //經過代碼建立C3P0數據庫鏈接池 23 /*ds = new ComboPooledDataSource(); 24 ds.setDriverClass("com.mysql.jdbc.Driver"); 25 ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy"); 26 ds.setUser("root"); 27 ds.setPassword("XDP"); 28 ds.setInitialPoolSize(10); 29 ds.setMinPoolSize(5); 30 ds.setMaxPoolSize(20);*/ 31 32 //經過讀取C3P0的xml配置文件建立數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下 33 //ds = new ComboPooledDataSource();//使用C3P0的默認配置來建立數據源 34 ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置來建立數據源 35 36 }catch (Exception e) { 37 throw new ExceptionInInitializerError(e); 38 } 39 } 40 41 /** 42 * @Method: getConnection 43 * @Description: 從數據源中獲取數據庫鏈接 44 * @Anthor:孤傲蒼狼 45 * @return Connection 46 * @throws SQLException 47 */ 48 public static Connection getConnection() throws SQLException{ 49 //從數據源中獲取數據庫鏈接 50 return ds.getConnection(); 51 } 52 53 /** 54 * @Method: release 55 * @Description: 釋放資源, 56 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 57 * @Anthor:孤傲蒼狼 58 * 59 * @param conn 60 * @param st 61 * @param rs 62 */ 63 public static void release(Connection conn,Statement st,ResultSet rs){ 64 if(rs!=null){ 65 try{ 66 //關閉存儲查詢結果的ResultSet對象 67 rs.close(); 68 }catch (Exception e) { 69 e.printStackTrace(); 70 } 71 rs = null; 72 } 73 if(st!=null){ 74 try{ 75 //關閉負責執行SQL命令的Statement對象 76 st.close(); 77 }catch (Exception e) { 78 e.printStackTrace(); 79 } 80 } 81 82 if(conn!=null){ 83 try{ 84 //將Connection鏈接對象還給數據庫鏈接池 85 conn.close(); 86 }catch (Exception e) { 87 e.printStackTrace(); 88 } 89 } 90 } 91 }
測試C3P0數據源
1 package me.gacl.test; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import org.junit.Test; 7 import me.gacl.util.JdbcUtils_C3P0; 8 import me.gacl.util.JdbcUtils_DBCP; 9 10 public class DataSourceTest { 11 12 @Test 13 public void c3p0DataSourceTest() { 14 Connection conn = null; 15 PreparedStatement st = null; 16 ResultSet rs = null; 17 try{ 18 //獲取數據庫鏈接 19 conn = JdbcUtils_C3P0.getConnection(); 20 String sql = "insert into test1(name) values(?)"; 21 st = conn.prepareStatement(sql); 22 st.setString(1, "gacl"); 23 st.executeUpdate(); 24 //獲取數據庫自動生成的主鍵 25 rs = st.getGeneratedKeys(); 26 if(rs.next()){ 27 System.out.println(rs.getInt(1)); 28 } 29 }catch (Exception e) { 30 e.printStackTrace(); 31 }finally{ 32 //釋放資源 33 JdbcUtils_C3P0.release(conn, st, rs); 34 } 35 } 36 }