一 介紹
上一篇文章中講到了通過Connection去訪問並操作資料庫,每次串連資料庫都是建立一個Connection對象,訪問完畢再close掉串連,如下:
這種方式的缺點很明顯:使用者每次請求都需要向資料庫獲得連結,而資料庫建立串連通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次串連,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢出、嚴重的話甚至宕機。
那有沒有什麼可最佳化的辦法呢。答案是肯定的,那就是 資料庫連接池技術(資料來源)。
二 使用資料庫連接池最佳化程式效能
原理:應用程式在啟動的時候就初始化一批Connection對象到一個池子中(集合),當應用程式需要訪問資料庫的時候就從這個池子中取Connection對象,操作資料庫完成後再將這個Connection對象對象放回到池子中去。這樣就可以避免重複建立Connection對象,如下:
三 實現自己的資料庫連接池
實現自己的串連池需實現java.sql.DataSource介面。DataSource介面中定義了兩個重載的getConnection方法:
Connection getConnection() Connection getConnection(String username, String password) 實現DataSource介面,並實現串連池功能的步驟:
在DataSource建構函式中大量建立與資料庫的串連,並把建立的串連加入LinkedList對象中。 實現getConnection方法,讓getConnection方法每次調用時,從LinkedList中取一個Connection返回給使用者。 當使用者使用完Connection,調用Connection.close()方法時,Collection對象應保證將自己返回到LinkedList中,而不要把conn還給資料庫。
package com.ricky;import java.io.InputStream;import java.io.PrintWriter;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.util.LinkedList;import java.util.Properties;import javax.sql.DataSource;import com.itheima.exception.NotSupportException;public class MyDataSource implements DataSource{private static LinkedList<Connection> pool = new LinkedList<Connection>();static{try{InputStream in = SimpleConnectionPool.class.getClassLoader().getResourceAsStream("dbcfg.properties");Properties props = new Properties();props.load(in);Class.forName(props.getProperty("className"));for(int i=0;i<10;i++){Connection conn = DriverManager.getConnection(props.getProperty("url"), props.getProperty("username"), props.getProperty("password"));pool.add(conn);}for(Connection conn:pool){System.out.println("初始化的串連為:"+conn);}}catch(Exception e){throw new ExceptionInInitializerError(e);}}public LinkedList<Connection> getPool(){return pool;}@Overridepublic synchronized Connection getConnection() throws SQLException {//conn.commit() conn.pro conn.close()if(pool.size()>0){final Connection conn = pool.removeFirst();//得到代理對象的執行個體Connection proxyConn = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {if(!method.getName().equals("close")){//調用原有對象的方法return method.invoke(conn, args);}else{//closereturn pool.add(conn);}}});System.out.println(proxyConn.getClass().getName());return proxyConn;}else{throw new RuntimeException("The Server is busy");}}@Overridepublic Connection getConnection(String username, String password)throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic PrintWriter getLogWriter() throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic int getLoginTimeout() throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {throw new NotSupportException("This method not supported by this datasource");}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {throw new NotSupportException("This method not supported by this datasource");}}
四 開來源資料庫串連池
現在很多WEB伺服器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即串連池的實現。通常我們把DataSource的實現,按其英文含義稱之為資料來源,資料來源中都包含了資料庫連接池的實現。
也有一些開源組織提供了資料來源的獨立實現:
DBCP 資料庫連接池 C3P0 資料庫連接池 實際應用時不需要編寫串連資料庫代碼,直接從資料來源獲得資料庫的串連。程式員編程時也應盡量使用這些資料來源的實現,以提升程式的資料庫訪問效能。
1、DBCP
DBCP 是 Apache 軟體基金組織下的開源串連池實現,Tomcat 的串連池正是採用該串連池來實現的。該資料庫連接池既可以與應用伺服器整合使用,也可由應用程式獨立使用。
官網地址:http://commons.apache.org/proper/commons-dbcp/
使用方法
1.1、dbcp.properties
#串連資料庫所用的 JDBC Driver Class,driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/day14username=rootpassword=sorry#<!-- 串連池啟動時建立的初始化串連數量(預設值為0) -->initialSize=10#串連池中可同時串連的最大的串連數,為0則表示沒有限制,預設為8maxActive=20#<!-- 串連池中最大的閒置串連數(預設為8,設 0 為沒有限制),超過的空閑串連將被釋放 -->maxIdle=15#<!-- 串連池中最小的閒置串連數(預設為0,一般可調整5) -->minIdle=5#<!-- 超過時間會丟出錯誤資訊 最大等待時間(單位為 ms) -->maxWait=60000connectionProperties=useUnicode=true;characterEncoding=UTF8#對於事務是否 autoCommit, 預設值為 truedefaultAutoCommit=true#對於資料庫是否只能讀取, 預設值為 falsedefaultReadOnly=#預設事物隔離幾本,取值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLEdefaultTransactionIsolation=REPEATABLE_READ
基礎參數說明
defaultAutoCommit: 對於事務是否 autoCommit, 預設值為 true
defaultReadOnly:對於資料庫是否只能讀取, 預設值為 false
initialSize:串連池啟動時建立的初始化串連數量(預設值為0)
driverClassName:串連資料庫所用的 JDBC Driver Class,
url: 串連資料庫的 URL
username:登陸資料庫所用的帳號
password: 登陸資料庫所用的密碼
maxActive: 串連池中可同時串連的最大的串連數,為0則表示沒有限制,預設為8
maxIdle: 串連池中最大的閒置串連數(預設為8,設 0 為沒有限制),超過的空閑串連將被釋放,如果設定為負數表示不限制(maxIdle不能設定太小,因為假如在高負載的情況下,串連的開啟時間比關閉的時間快,會引起串連池中idle的個數 上升超過maxIdle,而造成頻繁的串連銷毀和建立)
minIdle:串連池中最小的閒置串連數(預設為0,一般可調整5),低於這個數量會被建立新的串連(該參數越接近maxIdle,效能越好,因為串連的建立和銷毀,都是需要消耗資源的;但是不能太大,因為在機器很閒置時候,也會建立低於minidle個數的串連)
maxWait: 超過時間會丟出錯誤資訊 最大等待時間(單位為 ms),當沒有可用串連時,串連池等待串連釋放的最大時間,超過該時間限制會拋出異常,如果設定-1表示無限等待(預設為-1,一般可調整為60000ms,避免因線程池不夠用,而導致請求被無限制掛起)
validationQuery: 驗證串連是否成功, SQL SELECT 指令至少要返回一行
removeAbandoned:超過removeAbandonedTimeout時間後,是否進行沒用串連的回收(預設為false)
removeAbandonedTimeout: 超過時間限制,回收五用的串連(預設為 300秒),removeAbandoned 必須為 true
logAbandoned: 是否記錄中斷事件, 預設為 false
1.2 範例程式碼
package com.itheima.pool;import java.io.InputStream;import java.sql.Connection;import java.util.Properties;import javax.sql.DataSource;import org.apache.commons.dbcp.BasicDataSourceFactory;public class DBCPDemo1 {public static void main(String[] args) throws Exception {Properties props = new Properties();InputStream in = DBCPDemo1.class.getClassLoader().getResourceAsStream("dbcp.properties");props.load(in);BasicDataSourceFactory facotry = new BasicDataSourceFactory();DataSource ds = facotry.createDataSource(props);Connection conn = ds.getConnection();System.out.println(conn.getClass().getName());conn.close();//換回池中}}
2、C3P0
C3P0是一個更加強大、配置更靈活的資料來源,官網地址:http://www.mchange.com/projects/c3p0/
2.1 通過代碼設定各個配置項
ComboPooledDataSource cpds = new ComboPooledDataSource();cpds.setDriverClass(props.getProperty("driverClass"));cpds.setJdbcUrl(props.getProperty("jdbcUrl"));cpds.setUser(props.getProperty("user"));cpds.setPassword(props.getProperty("password"));
2.2 通過設定檔
類路徑下提供一個c3p0-config.xml檔案,如下:
<?xml version="1.0" encoding="UTF-8"?><c3p0-config><default-config><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql:///day14</property><property name="user">root</property><property name="password">sorry</property><property name="initialPoolSize">15</property><property name="maxIdleTime">30</property><property name="maxPoolSize">20</property><property name="minPoolSize">5</property><property name="maxStatements">2000</property></default-config> <!-- This app is massive! --><named-config name="day14"><property name="driverClass">com.mysql.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql:///day14</property><property name="user">root</property><property name="password">sorry</property><property name="initialPoolSize">15</property><property name="maxIdleTime">30</property><property name="maxPoolSize">20</property><property name="minPoolSize">5</property><property name="maxStatements">2000</property></named-config></c3p0-config>
擷取資料來源方法如下:
//ComboPooledDataSource ds = new ComboPooledDataSource();//擷取設定檔中的default-configComboPooledDataSource ds = new ComboPooledDataSource("day14");Connection conn = ds.getConnection();
更詳細的用法可參考這篇文章:http://my.oschina.net/lyzg/blog/55133
JDBC資料來源的講解到這裡就結束了,後續文章會介紹一下資料庫操作架構,例如DBUtils、MyBatis等等。