學習jdbc的鏈接池要弄清楚如下幾個問題:html
1、什麼是數據庫鏈接池,JDBC鏈接池?java
2、爲何使用數據庫鏈接池?mysql
3、怎樣使用鏈接池?sql
4、如何配置java開發中的鏈接池?數據庫
如下的篇幅將會對這幾個問題進行探索:apache
1、什麼是數據庫鏈接池,JDBC鏈接池?編程
一、 百度百科的解釋: 鏈接池是建立和管理一個鏈接的緩衝池的技術,這些鏈接準備好被任何須要它們的線程使用。tomcat
①、應用程序直接獲取數據庫鏈接的缺點服務器
用戶每次請求都須要向數據庫得到連接,而數據庫建立鏈接一般須要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量, 數據庫服務器就須要建立10萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機oracle
②、數據庫鏈接池的基本概念:
數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出.對數據庫鏈接的管理能顯著影響到整個 應用程序的伸縮性和健壯性,影響到程序的性能指標.數據庫鏈接池正式針對這個問題提出來的.數據庫鏈接池負責分配,管理和釋放數據庫鏈接, 它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個
數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中, 這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的.不管這 些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量.鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數 ,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中.
③、數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:
1.最小鏈接數:是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費. 2.最大鏈接數:是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過次數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響 之後的數據庫操做 3.若是最小鏈接數與最大鏈接數相差很大:那麼最早鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫 鏈接.不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,他將被放到鏈接池中等待重複使用或是空間超時後被釋放.
④、編寫數據庫鏈接池:
編寫鏈接池需實現java.sql.DataSource接口。DataSource接口中定義了兩個重載的getConnection方法:
•Connection getConnection() •Connection getConnection(String username, String password)
實現DataSource接口,並實現鏈接池功能的步驟:
1.在DataSource構造函數中批量建立與數據庫的鏈接,並把建立的鏈接加入LinkedList對象中。 2.實現getConnection方法,讓getConnection方法每次調用時,從LinkedList中取一個Connection返回給用戶。 3.當用戶使用完Connection,調用Connection.close()方法時,Collection對象應保證將本身返回到LinkedList中,而不要把conn還給數據庫。Collection保證將本身返回到LinkedList中是此處編程的難點。
數據庫鏈接池核心代碼
使用動態代理技術構建鏈接池中的connection:
proxyConn = (Connection) Proxy.newProxyInstance(this.getClass() .getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { //此處爲內部類,當close方法被調用時將conn還回池中,其它方法直接執行 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("close")) { pool.addLast(conn); return null; } return method.invoke(conn, args); } });
數據庫鏈接池編寫範例:
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配置文件以下:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcStudy username=root password=XDP jdbcPoolInitSize=10
寫一個JdbcUtil測試數據庫鏈接池:
package me.gacl.utils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import me.gacl.demo.JdbcPool; public class JdbcUtil { /** * @Field: pool * 數據庫鏈接池 */ private static JdbcPool pool = new JdbcPool(); /** * @Method: getConnection * @Description: 從數據庫鏈接池中獲取數據庫鏈接對象 * @Anthor:孤傲蒼狼 * @return Connection數據庫鏈接對象 * @throws SQLException */ public static Connection getConnection() throws SQLException{ return pool.getConnection(); } /** * @Method: release * @Description: 釋放資源, * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 * @Anthor:孤傲蒼狼 * * @param conn * @param st * @param rs */ public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ //關閉存儲查詢結果的ResultSet對象 rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ //關閉負責執行SQL命令的Statement對象 st.close(); }catch (Exception e) { e.printStackTrace(); } } if(conn!=null){ try{ //關閉Connection數據庫鏈接對象 conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
⑤、開源數據庫鏈接池:
如今不少WEB服務器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即鏈接池的實現。一般咱們把DataSource的實現,按其英文含義稱之爲數據源,數據源中都包含了數據庫鏈接池的實現。 也有一些開源組織提供了數據源的獨立實現:
在使用了數據庫鏈接池以後,在項目的實際開發中就不須要編寫鏈接數據庫的代碼了,直接從數據源得到數據庫的鏈接。
一、DBCP數據源的使用:
DBCP 是 Apache 軟件基金組織下的開源鏈接池實現,要使用DBCP數據源,須要應用程序應在系統中增長以下兩個 jar 文件:
Tomcat 的鏈接池正是採用該鏈接池來實現的。該數據庫鏈接池既能夠與應用服務器整合使用,也可由應用程序獨立使用。
二、在應用程序中加入dbcp鏈接池
1.導入相關jar包
commons-dbcp-1.2.2.jar、commons-pool.jar
二、在類目錄下加入dbcp的配置文件:dbcpconfig.properties
dbcpconfig.properties的配置信息以下:
#鏈接設置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcstudy username=root password=XDP #<!-- 初始化鏈接 --> initialSize=10 #最大鏈接數量 maxActive=50 #<!-- 最大空閒鏈接 --> maxIdle=20 #<!-- 最小空閒鏈接 --> minIdle=5 #<!-- 超時等待時間以毫秒爲單位 6000毫秒/1000等於60秒 --> maxWait=60000 #JDBC驅動創建鏈接時附帶的鏈接屬性屬性的格式必須爲這樣:[屬性名=property;] #注意:"user" 與 "password" 兩個屬性會被明確地傳遞,所以這裏不須要包含他們。 connectionProperties=useUnicode=true;characterEncoding=UTF8 #指定由鏈接池所建立的鏈接的自動提交(auto-commit)狀態。 defaultAutoCommit=true #driver default 指定由鏈接池所建立的鏈接的只讀(read-only)狀態。 #若是沒有設置該值,則「setReadOnly」方法將不被調用。(某些驅動並不支持只讀模式,如:Informix) defaultReadOnly= #driver default 指定由鏈接池所建立的鏈接的事務級別(TransactionIsolation)。 #可用值爲下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED
以下圖所示:
在獲取數據庫鏈接的工具類(如jdbcUtils)的靜態代碼塊中建立池:
package me.gacl.util; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; /** * @ClassName: JdbcUtils_DBCP * @Description: 數據庫鏈接工具類 * @author: 孤傲蒼狼 * @date: 2014-10-4 下午6:04:36 * */ public class JdbcUtils_DBCP { /** * 在java中,編寫數據庫鏈接池需實現java.sql.DataSource接口,每一種數據庫鏈接池都是DataSource接口的實現 * DBCP鏈接池就是java.sql.DataSource接口的一個具體實現 */ private static DataSource ds = null; //在靜態代碼塊中建立數據庫鏈接池 static{ try{ //加載dbcpconfig.properties配置文件 InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); Properties prop = new Properties(); prop.load(in); //建立數據源 ds = BasicDataSourceFactory.createDataSource(prop); }catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * @Method: getConnection * @Description: 從數據源中獲取數據庫鏈接 * @Anthor:孤傲蒼狼 * @return Connection * @throws SQLException */ public static Connection getConnection() throws SQLException{ //從數據源中獲取數據庫鏈接 return ds.getConnection(); } /** * @Method: release * @Description: 釋放資源, * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 * @Anthor:孤傲蒼狼 * * @param conn * @param st * @param rs */ public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ //關閉存儲查詢結果的ResultSet對象 rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ //關閉負責執行SQL命令的Statement對象 st.close(); }catch (Exception e) { e.printStackTrace(); } } if(conn!=null){ try{ //將Connection鏈接對象還給數據庫鏈接池 conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
測試DBCP數據源:
package me.gacl.test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org.junit.Test; import me.gacl.util.JdbcUtils_DBCP; public class DataSourceTest { @Test public void dbcpDataSourceTest() { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try{ //獲取數據庫鏈接 conn = JdbcUtils_DBCP.getConnection(); String sql = "insert into test1(name) values(?)"; st = conn.prepareStatement(sql); st.setString(1, "gacl"); st.executeUpdate(); //獲取數據庫自動生成的主鍵 rs = st.getGeneratedKeys(); if(rs.next()){ System.out.println(rs.getInt(1)); } }catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 JdbcUtils_DBCP.release(conn, st, rs); } } }
C3P0數據源
C3P0是一個開源的JDBC鏈接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。C3P0數據源在項目開發中使用得比較多。
在應用程序中加入C3P0鏈接池
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的配置信息以下:
<?xml version="1.0" encoding="UTF-8"?> <!-- c3p0-config.xml必須位於類路徑下面 private static ComboPooledDataSource ds; static{ try { ds = new ComboPooledDataSource("MySQL"); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } --> <c3p0-config> <!-- C3P0的缺省(默認)配置, 若是在代碼中「ComboPooledDataSource ds = new ComboPooledDataSource();」這樣寫就表示使用的是C3P0的缺省(默認)配置信息來建立數據源 --> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> <property name="user">root</property> <property name="password">XDP</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <!-- C3P0的命名配置, 若是在代碼中「ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");」這樣寫就表示使用的是name是MySQL的配置信息來建立數據源 --> <named-config name="MySQL"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> <property name="user">root</property> <property name="password">XDP</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> </c3p0-config>
以下圖所示:
在獲取數據庫鏈接的工具類(如jdbcUtils)的靜態代碼塊中建立池
package me.gacl.util; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * @ClassName: JdbcUtils_C3P0 * @Description: 數據庫鏈接工具類 * @author: 孤傲蒼狼 * @date: 2014-10-4 下午6:04:36 * */ public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; //在靜態代碼塊中建立數據庫鏈接池 static{ try{ //經過代碼建立C3P0數據庫鏈接池 /*ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy"); ds.setUser("root"); ds.setPassword("XDP"); ds.setInitialPoolSize(10); ds.setMinPoolSize(5); ds.setMaxPoolSize(20);*/ //經過讀取C3P0的xml配置文件建立數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下 //ds = new ComboPooledDataSource();//使用C3P0的默認配置來建立數據源 ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置來建立數據源 }catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * @Method: getConnection * @Description: 從數據源中獲取數據庫鏈接 * @Anthor:孤傲蒼狼 * @return Connection * @throws SQLException */ public static Connection getConnection() throws SQLException{ //從數據源中獲取數據庫鏈接 return ds.getConnection(); } /** * @Method: release * @Description: 釋放資源, * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 * @Anthor:孤傲蒼狼 * * @param conn * @param st * @param rs */ public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ //關閉存儲查詢結果的ResultSet對象 rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ //關閉負責執行SQL命令的Statement對象 st.close(); }catch (Exception e) { e.printStackTrace(); } } if(conn!=null){ try{ //將Connection鏈接對象還給數據庫鏈接池 conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
測試C3P0數據源
package me.gacl.test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org.junit.Test; import me.gacl.util.JdbcUtils_C3P0; import me.gacl.util.JdbcUtils_DBCP; public class DataSourceTest { @Test public void c3p0DataSourceTest() { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try{ //獲取數據庫鏈接 conn = JdbcUtils_C3P0.getConnection(); String sql = "insert into test1(name) values(?)"; st = conn.prepareStatement(sql); st.setString(1, "gacl"); st.executeUpdate(); //獲取數據庫自動生成的主鍵 rs = st.getGeneratedKeys(); if(rs.next()){ System.out.println(rs.getInt(1)); } }catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 JdbcUtils_C3P0.release(conn, st, rs); } } }
配置Tomcat數據源
在實際開發中,咱們有時候還會使用服務器提供給咱們的數據庫鏈接池,好比咱們但願Tomcat服務器在啓動的時候能夠幫咱們建立一個數據庫鏈接池,那麼咱們在應用程序中就不須要手動去建立數據庫鏈接池,直接使用Tomcat服務器建立好的數據庫鏈接池便可。要想讓Tomcat服務器在啓動的時候幫咱們建立一個數據庫鏈接池,那麼須要簡單配置一下Tomcat服務器。
JNDI技術簡介
JNDI(Java Naming and Directory Interface),Java命名和目錄接口,它對應於J2SE中的javax.naming包, 這 套API的主要做用在於:它能夠把Java對象放在一個容器中(JNDI容器),併爲容器中的java對象取一個名稱,之後程序想得到Java對象,只需 經過名稱檢索便可。其核心API爲Context,它表明JNDI容器,其lookup方法爲檢索容器中對應名稱的對象。
Tomcat服務器建立的數據源是以JNDI資源的形式發佈的,因此說在Tomat服務器中配置一個數據源實際上就是在配置一個JNDI資源,經過查看Tomcat文檔,咱們知道使用以下的方式配置tomcat服務器的數據源:
<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>
服務器建立好數據源以後,咱們的應用程序又該怎麼樣獲得這個數據源呢,Tomcat服務器建立好數據源以後是以JNDI的形式綁定到一個JNDI容器中的,咱們能夠把JNDI想象成一個大大的容器,咱們能夠往這個容器中存放一些對象,一些資源,JNDI容器中存放的對象和資源都會有一個獨一無二的名稱,應用程序想從JNDI容器中獲取資源時,只須要告訴JNDI容器要獲取的資源的名稱,JNDI根據名稱去找到對應的資源後返回給應用程序。咱們平時作javaEE開發時,服務器會爲咱們的應用程序建立不少資源,好比request對象,response對象,服務器建立的這些資源有兩種方式提供給咱們的應用程序使用:第一種是經過方法參數的形式傳遞進來,好比咱們在Servlet中寫的doPost和doGet方法中使用到的request對象和response對象就是服務器以參數的形式傳遞給咱們的。第二種就是JNDI的方式,服務器把建立好的資源綁定到JNDI容器中去,應用程序想要使用資源時,就直接從JNDI容器中獲取相應的資源便可。
對於上面的name="jdbc/datasource"數據源資源,在應用程序中能夠用以下的代碼去獲取
1 Context initCtx = new InitialContext(); 2 Context envCtx = (Context) initCtx.lookup("java:comp/env"); 3 dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此文以上大多數內容引用自: http://www.cnblogs.com/xdp-gacl/p/4002804.html,在此感謝做者的分享!