最近,本人着手開發要有一個有強大後臺的網站,在使用連接池時,覺得使用服務器自帶的連接池總有些受限制。同時,爲了加深對Java的學習和研究。寫下了下面的連接池類。 該連接池主要有一下功能; 1)初始化一次,到處使用。 2)強大的日誌功能,記錄每一個sql動作,包括Connection、ResultSet 和Statement 3)根據連接的數量,定時自動回收已經釋放或超時的連接。 4)配置靈活,可以使用各種JDBC驅動程序,支持多驅動程序。
源代碼:
/* * @Title 連接池 * @Author: zxg * @Version 1.0 * @Memo:定義數據庫連接及其數據庫連接池等 */ package com.drsl.db;
import java.io.*; import java.sql.*; import java.util.*; import java.util.Date; import java.lang.reflect.*; import com.mysql.jdbc.Driver;
import com.drsl.db.*;
public class ConnectionManager { static private ConnectionManager instance; // 唯一實例 static private int clients; static private int maxonlinetime=30*60*1000; private Vector drivers = new Vector(); private Hashtable pools = new Hashtable(); private Timer checkConnTimer=new Timer(); static private PrintWriter log; /** * 返回唯一實例.如果是第一次調用此方法,則創建實例 * * @return ConnectionManager 唯一實例 */ static synchronized public ConnectionManager getInstance() { if (instance == null) { instance = new ConnectionManager(); } // clients++; return instance; } /** * 建構函數私有以防止其它對象創建本類實例 */ private ConnectionManager() { init(); } /** * 讀取屬性完成初始化 */ private void init() { try { InputStream is = getClass().getResourceAsStream("db.properties"); Properties dbProps = new Properties(); dbProps.load(is); } catch (Exception e) { e.printStackTrace(); System.err.println("不能讀取屬性文件= " + "請確保db.properties在CLASSPATH指定的路徑中"); return; } String logFile = dbProps.getProperty("logfile", "log.txt"); try { log = new PrintWriter(new FileWriter(logFile, true), true); } catch (IOException e) { System.err.println("無法打開日誌文件: " + logFile); log = new PrintWriter(System.err); } loadDrivers(dbProps); createPools(dbProps); } /** * 裝載和註冊所有JDBC驅動程序 * * @param props 屬性 */ private void loadDrivers(Properties props) { String driverClasses = props.getProperty("drivers"); StringTokenizer st = new StringTokenizer(driverClasses); while (st.hasMoreElements()) { String driverClassName = st.nextToken().trim(); try { Driver driver = (Driver)Class.forName(driverClassName).newInstance(); if(driver!=null){ DriverManager.registerDriver(driver); drivers.addElement(driver); log("Begin"); log("成功註冊JDBC驅動程序" + driverClassName); } else{ log("Begin"); log("註冊JDBC驅動程序" + driverClassName+"失敗"); } } catch (Exception e) { log("Begin"); log("無法註冊JDBC驅動程序: " + driverClassName + ", 錯誤: " + e); } } } /** * 根據指定屬性創建連接池實例. * * @param props 連接池屬性 */ private void createPools(Properties props) { Enumeration propNames = props.propertyNames(); while (propNames.hasMoreElements()) { String name = (String) propNames.nextElement(); if (name.endsWith(".url")) { String poolName = name.substring(0, name.lastIndexOf(".")); String url = props.getProperty(poolName + ".url"); if (url == null) { log("沒有爲連接池" + poolName + "指定URL"); continue; } String user = props.getProperty(poolName + ".user"); String password = props.getProperty(poolName + ".password"); String maxconn = props.getProperty(poolName + ".maxconn", "0"); String minconn = props.getProperty(poolName + ".minconn", "10"); String option=props.getProperty(poolName+".option",""); int max,min; try { max = Integer.valueOf(maxconn).intValue(); } catch (NumberFormatException e) { log("錯誤的最大連接數限制: " + maxconn + " .連接池: " + poolName); max = 0; } try { min = Integer.valueOf(minconn).intValue(); } catch (NumberFormatException e) { log("錯誤的最小連接數限制: " + minconn + " .連接池: " + poolName); min = 0; } try{ ConnectionPool pool = new ConnectionPool(poolName, url,user,password,min,max,option); pools.put(poolName, pool); //2秒鐘後開始每個一分鐘檢查一次連接池情況 checkConnTimer.schedule(pool,2000,60*1000);
log("成功創建連接池" + poolName); }catch(Exception e){ log(e,"創建DBConnectionPool出錯"); } } } }
/** * 將連接對象返回給由名字指定的連接池 * * @param name 在屬性文件中定義的連接池名字 * @param con 連接對象 */ public void freeConnection(String name, Connection conn) { ConnectionPool pool = (ConnectionPool) pools.get(name); if (pool != null) { pool.freeConnection(conn); } } /** * 獲得一個可用的(空閒的)連接.如果沒有可用連接,且已有連接數小於最大連接數 * 限制,則創建並返回新連接 * * @param name 在屬性文件中定義的連接池名字 * @return Connection 可用連接或null */ public Connection getConnection(String name) { ConnectionPool pool = (ConnectionPool) pools.get(name); if (pool != null) { return pool.getConnection(); } return null; } /** * 獲得一個可用連接.若沒有可用連接,且已有連接數小於最大連接數限制, * 則創建並返回新連接.否則,在指定的時間內等待其它線程釋放連接. * * @param name 連接池名字 * @param time 以毫秒計的等待時間 * @return Connection 可用連接或null */ public Connection getConnection(String name, long time) { ConnectionPool pool = (ConnectionPool) pools.get(name); if (pool != null) { return pool.getConnection(time); } return null; } /** * 關閉所有連接,撤銷驅動程序的註冊 */ public synchronized void release() { // 等待直到最後一個客戶程序調用 // if (--clients != 0) { // return; // } checkConnTimer.cancel(); Enumeration allPools = pools.elements(); while (allPools.hasMoreElements()) { ConnectionPool pool = (ConnectionPool) allPools.nextElement(); pool.cancel(); pool.release(); } Enumeration allDrivers = drivers.elements(); while (allDrivers.hasMoreElements()) { Driver driver = (Driver) allDrivers.nextElement(); try { DriverManager.deregisterDriver(driver); log("撤銷JDBC驅動程序 " + driver.getClass().getName()+"的註冊"); } catch (SQLException e) { log(e,"無法撤銷下列JDBC驅動程序的註冊: " + driver.getClass().getName()); } }
} /** * 將文本信息寫入日誌文件 */ static public void log(String msg) { log.println(new Date() + ": " + msg); } /** * 將文本信息與異常寫入日誌文件 */ static public void log(Throwable e, String msg) { log.println(new Date() + ": " + msg); e.printStackTrace(log); } //測試
//////////////////////////////////////////////////////////////////////////////////////////////// static public void main(String[] args) { ConnectionManager dcm=null; try{ dcm=ConnectionManager.getInstance(); Connection conn=dcm.getConnection("mysql"); Connection conn1=dcm.getConnection("mysql"); Connection conn2=dcm.getConnection("mysql"); Connection conn3=dcm.getConnection("mysql"); Connection conn4=dcm.getConnection("mysql"); Connection conn5=dcm.getConnection("mysql"); Connection conn6=dcm.getConnection("mysql"); Connection conn7=dcm.getConnection("mysql"); Connection conn8=dcm.getConnection("mysql"); Connection conn9=dcm.getConnection("mysql"); Connection conn10=dcm.getConnection("mysql"); Connection conn11=dcm.getConnection("mysql"); Connection conn12=dcm.getConnection("mysql"); Connection conn13=dcm.getConnection("mysql"); Connection conn14=dcm.getConnection("mysql"); ResultSet rs; String sql="select * from css"; Statement st=conn.createStatement(); if(st==null) { log("main--error while get /"Statement/""); return; } rs=st.executeQuery(sql); if(rs==null){ log("main--error while get /"ResultSet/""); return; } System.out.println("/r/n"); while(rs.next()){ System.out.println(rs.getString(1)); } rs.close(); st.close(); rs=null; st=null; conn.close(); conn1.close(); conn2.close(); conn3.close(); conn4.close(); conn5.close(); conn6.close(); conn7.close(); conn8.close(); conn9.close(); conn10.close(); conn11.close(); conn12.close(); conn13.close(); conn14.close(); conn=null; conn1=null; conn2=null; conn3=null; conn4=null; conn5=null; conn6=null; conn7=null; conn8=null; conn9=null; conn10=null; conn11=null; conn12=null; conn13=null; conn14=null; }catch(SQLException e){ dcm.log(e,"main--error"); } }
}
//////////////////////////////////////////////////////////
/***************連接池類*************************************************/
/** * 此類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最 * 大連接數爲止.在返回連接給客戶程序之前,它能夠驗證連接的有效性. * 它繼承自 TimerTask 被 ConnectionManager 類的timer成員調度 */ package com.drsl.db;
import java.io.*; import java.sql.*; import java.util.*; import java.util.Date; import java.lang.reflect.*;
import com.drsl.db.*;
public class ConnectionPool extends TimerTask{ private int countConn; private Vector freeConns = new Vector(); private Vector usingConns = new Vector(); private int maxUseTime;//使用中的連接最大空閒時間 private int maxFreeTime;//空閒的連接最大空閒時間(在連接數未小於最小連接數時,關閉此連接) private int maxConn;//最大連接數 private int minConn;//最小連接數 private String name;//pool name private String url; private String user; private String password; private String option; // private PrintWriter log;
/** * 創建新的連接池 * * @param name 連接池名字 * @param url 數據庫的JDBC url * @param dbInfo 數據庫連接信息 * @param maxConn 此連接池允許建立的最大連接數 */ public ConnectionPool(String name, String url,String user,String password, int minConn,int maxConn,String option) { this.name = name; this.url = url; this.user = user; this.password = password; this.option=option; this.maxConn = maxConn; this.minConn = minConn; if(this.minConn<=0) this.minConn=10; log("End One Part/r/n"); for(int i=0; i<minConn;i++){ newConnection(); } } /** * 將新建的連接添加到連接池 * * @param connobj 新建的連接 */ public synchronized void freeConnection(ConnectionObject connobj) { // 將指定連接加入到向量末尾 try{ connobj.setInUse(false); freeConns.addElement(connobj); log("成功記錄一個新建連接或者回收一個已釋放連接"); notifyAll(); }catch(ArrayIndexOutOfBoundsException e){ log(e,"freeConnection(ConnectionObject connobj) --失敗"); } } /** * 將不再使用的連接返回給連接池 * * @param conn 客戶程序主動釋放的連接 */ public synchronized void freeConnection(Connection conn) { // 將指定連接加入到向量末尾 ConnectionObject connobj=null; for(int i=0;i<usingConns.size();i++) { connobj=(ConnectionObject)usingConns.get(i); if(connobj.getConnection()==conn) break; } if(connobj!=null){ try{ connobj.setInUse(false); freeConns.addElement(connobj); usingConns.removeElement(connobj); log("成功回收一個連接"); notifyAll(); }catch(Exception e){ log(e,"回收一個連接--失敗"); } } } /** * 從連接池獲得一個可用連接.如沒有空閒的連接且當前連接數小於最大連接 * 數限制,則創建新連接.如原來登記爲可用的連接不再有效,則從向量刪除之, * 然後遞歸調用自己以嘗試新的可用連接. */ public synchronized Connection getConnection() { ConnectionObject connobj = null; Connection conn=null; // 獲取向量中第一個可用連接 try { connobj = (ConnectionObject) freeConns.get(0); } catch (Exception e) { log("End One Part/r/n");
log("從連接池" + name+"獲取一個連接失敗"); if( maxConn == 0 || countConn < maxConn) { connobj = newConnection(); } } //如沒有空閒的連接且當前連接數小於最大連接數限制,則創建新連接 if(connobj==null && ( maxConn == 0 || countConn < maxConn)) { log("從連接池" + name+"獲取一個連接失敗"); log("End One Part/r/n"); connobj = newConnection(); } if (connobj != null) { connobj.setLastAccessTime(new Date().getTime()); connobj.setInUse(true); usingConns.addElement(connobj); freeConns.removeElementAt(0); conn=connobj.getConnection(); return conn; }else{ log("獲取連接" + name+"失敗--連接數量已達最大上限"); return null; } } /** * 從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間 * 參見前一個getConnection()方法. * * @param timeout 以毫秒計的等待時間限制 */ public synchronized Connection getConnection(long timeout) { long startTime = new Date().getTime(); Connection conn=null; while ((conn = getConnection()) == null) { try { wait(timeout);//?????????????? } catch (InterruptedException e){ } if ((new Date().getTime() - startTime) >= timeout) { // wait()返回的原因是超時????????? return null; } } return conn; } /** * 關閉所有連接 */ public synchronized void release() { // cancel(); Enumeration allConnections = freeConns.elements(); while (allConnections.hasMoreElements()) { ConnectionObject connobj = (ConnectionObject) allConnections.nextElement(); try { connobj.close(); connobj=null; log("關閉連接池" + name+"中的一個連接"); } catch (SQLException e) {//SQLException log(e, "無法關閉連接池" + name+"中的連接"); } } freeConns.removeAllElements(); // allConnections = usingConns.elements(); while (allConnections.hasMoreElements()) { ConnectionObject connobj = (ConnectionObject) allConnections.nextElement(); try { connobj.close(); connobj=null; log("關閉連接池" + name+"中的一個連接"); } catch (SQLException e) {//SQLException log(e, "無法關閉連接池" + name+"中的連接"); } } usingConns.removeAllElements(); } /** * 創建新的連接 */ private ConnectionObject newConnection() { ConnectionObject connobj= null; try { log("連接池" + name+"創建一個新的連接對象"); String URL=url+option; log("URL=" +URL ); Connection conn = DriverManager.getConnection(URL,user,password); connobj=new ConnectionObject(conn,false); freeConnection(connobj); countConn++; } catch (SQLException e) { log(e, "無法創建下列URL的連接: " + url+" for User= " +user+" Password="+password); return null; } return connobj; } //檢查各連接狀態(每分鐘一次) public void run (){ ConnectionObject connobj=null; //回收 正在使用中的已經"關閉"的連接 //和 使用時間已經超時的連接 int i=0; while(i<usingConns.size()){
connobj=(ConnectionObject)usingConns.get(i); if(connobj.isInUse()==false){ try{ log("run--回收 正在使用中的已經/"關閉/"的連接"); freeConnection(connobj); usingConns.removeElementAt(i); i--; }catch(ArrayIndexOutOfBoundsException e){ log(e,"run--回收 正在使用中的已經/"關閉/"的連接--失敗"); } }else{ long nowtime=new Date().getTime(); long t=nowtime-connobj.getLastAccessTime(); try{ if(t>20*60*1000){//超時時間爲20分鐘 log("run--回收 使用時間已經超時的連接"); freeConnection(connobj); usingConns.removeElementAt(i); i--; } }catch(ArrayIndexOutOfBoundsException e ){ log(e,"run--回收 使用時間已經超時的連接--失敗"); } } i++; } //刪除 空閒的已經被意外關閉的連接 i=0; while(i<freeConns.size()){ connobj= (ConnectionObject)freeConns.get(i); try{ if(connobj.getConnection().isClosed()){ connobj=null; freeConns.removeElementAt(i); countConn--; i--; log("run--刪除 空閒的已經被意外關閉的連接"); } }catch(Exception e){ log(e,"run--刪除 空閒的已經被意外關閉的連接-失敗"); } i++; } //刪除 從空閒連接中多餘的(大於最小連接數的)連接 long cc=countConn-minConn; i=0; while(i<cc && freeConns.size()>1){ try{ connobj=(ConnectionObject)freeConns.get(0); connobj.close(); connobj=null; freeConns.removeElementAt(0); countConn--; log("run--刪除 從空閒連接中多餘的(大於最小連接數的)連接 "); }catch(SQLException e){ log(e,"run--從空閒連接中多餘的(大於最小連接數的)連接--失敗"); } i++; } //增加連接 保持要求的最小連接數 if(cc<0){ cc=-cc; log("End One Part/r/n"); log("run--增加連接 保持要求的最小連接數"); for(i=0;i<cc;i++){ newConnection(); } } //增加連接 保持至少有一個可用連接 if(freeConns.size()<1){ log("End One Part/r/n"); log("run--增加連接 保持至少有一個可用連接"); newConnection(); } log("run--once"); // notifyAll(); } /** * 將文本信息寫入日誌文件 */ private void log(String msg) { ConnectionManager.log(msg); } /** * 將文本信息與異常寫入日誌文件 */ private void log(Throwable e, String msg) { ConnectionManager.log(e,msg); }
}
/////////////////////////////////////////////////////////////
/** * 數據連接的自封裝,屏蔽了close方法和createStatement,prepareStatement 方法以返回自己的接管類 * @author zxg */ package com.drsl.db;
import java.io.*; import java.sql.*; import java.util.*; import java.util.Date; import java.lang.reflect.*;
public class ConnectionObject implements InvocationHandler{ private final static String CLOSE_METHOD_NAME = "close"; private final static String CREATSTATMENT_METHOD_NAME = "createStatement"; private final static String PREPARESTATEMENT_METHOD_NAME = "prepareStatement"; private Connection conn = null; private Connection conn_proxy=null; private PreparedStatementObject pso=null; private StatementObject stmo=null; //數據庫的忙狀態 private boolean inUse = false; //用戶最後一次訪問該連接方法的時間 private static long lastAccessTime = new Date().getTime();
public ConnectionObject(Connection conn, boolean inUse){ this.conn = conn; this.inUse = inUse; } /** * Returns the conn. * @return Connection */ //返回數據庫連接conn的接管類,以便截住Connection 的各個方法 public Connection getConnection() { if(conn_proxy==null){ ClassLoader classloader=conn.getClass().getClassLoader(); Class[] interfaces = conn.getClass().getInterfaces(); if(interfaces==null||interfaces.length==0){ interfaces = new Class[1]; interfaces[0] = Connection.class; } try{ conn_proxy= (Connection)Proxy.newProxyInstance(classloader,interfaces,this); }catch(NullPointerException e){ log(e,"ConnectionObject getConnection()--error"); } if(conn_proxy!=null) log("ConnectionObject getConnection()--success"); } return conn_proxy; } /** * 該方法真正的關閉了數據庫的連接 * @throws SQLException */ synchronized void close() throws SQLException{ //由於類屬性conn是沒有被接管的連接,因此一旦調用close方法後就直接關閉連接 conn.close(); conn=null; } /** * Returns the inUse. * @return boolean */ public boolean isInUse() { return inUse; }
/** * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object) */ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { Object obj = null; log("ConnectionObject--invoke:Method: /""+m.getName()+"/""); //判斷是否調用了close的方法,如果調用close方法則把連接置爲無用狀態 if(CLOSE_METHOD_NAME.equals(m.getName())){ setInUse(false); }else if(CREATSTATMENT_METHOD_NAME.equals(m.getName())){ //如果調用了 createStatment 的方法,封裝 Statement 類接受管理 Statement stm=(Statement) m.invoke(conn, args); if(stm!=null && stmo==null){ stmo=new StatementObject(stm); obj=stmo.getStatement(); }else{ ConnectionManager.log("ConnectionObject--invoke:Method: /""+m.getName()+"/"--失敗"); } }else if(PREPARESTATEMENT_METHOD_NAME.equals(m.getName())){ //如果調用了 createStatment 的方法,封裝 PreparedStatement 類接受管理 PreparedStatement ps=(PreparedStatement) m.invoke(conn, args); if(ps!=null && pso==null){ pso=new PreparedStatementObject(ps); } else if(pso!=null){ obj=pso.getPreparedStatement(); }else log("ConnectionObject--invoke:Method: /""+m.getName()+"/"--失敗"); }else obj = m.invoke(conn, args); //設置最後一次訪問時間,以便及時清除超時的連接 lastAccessTime = new Date().getTime(); return obj; } /** * Returns the lastAccessTime. * @return long */ public static long getLastAccessTime() { return lastAccessTime; } /** * set the lastAccessTime. * @param latimelong */ public static void setLastAccessTime(long latime) { lastAccessTime=latime; }
/** * Sets the inUse. * @param inUse The inUse to set */ public void setInUse(boolean inUse) { this.inUse = inUse; } public synchronized void release() { try{ close(); }catch(SQLException e){ log(e,"ConnectionObject--release 調用 close 失敗"); } } /** * 將文本信息寫入日誌文件 */ private void log(String msg) { ConnectionManager.log(msg); } /** * 將文本信息與異常寫入日誌文件 */ private void log(Throwable e, String msg) { ConnectionManager.log(e,msg); }
}
待續。。。
|
|