連接池是非常好的想法,應用很普遍。自己寫一個數據庫連接池,並不像想象中那樣困難。一般系統對連接池的功能不會有太多要求,使用自己的連接池未必是個壞主意。下面以Oracle為例,但是對Teradata和Greenplum也是可行的。另外我還實現了連接有效性檢查(checkConn)和恢復連接(resetConn)的方法。本例編程采用的是JRE1.4.2環境(別忘了准備訪問數據庫的jar包)。有任何問題請隨時留言,歡迎探討。
在Oracle內創建測試數據:
drop table my_table; create table my_table( field_id varchar2(3), field_content varchar2(60), record_create_date date default sysdate ); insert into my_table(field_id,field_content) values('001','this is first record'); insert into my_table(field_id,field_content) values('002','this is second record'); insert into my_table(field_id,field_content) values('003','this is third record'); commit;
DBPool.java:
package dataWebService; import java.sql.DriverManager; import java.util.Date; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; public class DBPool{ private String cls; private String url; private String usr; private String pss; private int connCount = 3;//連接數 private Connection[] connections;//保存數據庫連接 private String[] connStatus;// 已連可用Y 已連不可用N 未連接X private Date[] lastQueryTime;//時間戳 public DBPool(DBPoolConfiguration poolConfiguration){ this.connCount=poolConfiguration.getConnCount(); this.cls=poolConfiguration.getCls(); this.url=poolConfiguration.getUrl(); this.usr=poolConfiguration.getUsr(); this.pss=poolConfiguration.getPss(); this.connections=new Connection[this.connCount]; this.connStatus=new String[this.connCount]; for(int i=0;i<this.connCount;i++){ this.connStatus[i]="X";//初始化全部未連接 } this.lastQueryTime = new Date[this.connCount]; } public DBPool(String cls,String url,String usr,String pss){ this.cls=cls; this.url=url; this.usr=usr; this.pss=pss; this.connections=new Connection[this.connCount]; this.connStatus=new String[this.connCount]; for(int i=0;i<this.connCount;i++){ this.connStatus[i]="X";//初始化全部未連接 } this.lastQueryTime = new Date[this.connCount]; } public void initPool(){ if(connCount<1){ System.out.println("請正確設置連接池窗口個數"); }else{ try{ Class.forName(this.cls);//register class }catch(ClassNotFoundException e){ System.out.println(e.getMessage()); }catch(Exception e){ System.out.println(e.getMessage());//other exceptions } for(int i=0;i<this.connCount;i++){ try{ this.connections[i]=DriverManager.getConnection(this.url, this.usr, this.pss); this.connStatus[i]="Y"; }catch(SQLException e){ System.out.println(e.getMessage()); }catch(Exception e){ System.out.println(e.getMessage());//other exceptions } } System.out.println("initPool is ready..."); }//end if } public void freePool(){ for(int i=0;i<this.connCount;i++){ try{ this.connections[i].commit(); this.connections[i].close(); this.connStatus[i]="X"; this.lastQueryTime[i]=null; }catch(Exception e){ try{ this.connections[i].close(); this.connStatus[i]="X"; this.lastQueryTime[i]=null; }catch(Exception e1){ System.out.println(e1.getMessage());//just for catch } } } System.out.println("freePool is over ..."); } public DBPoolConnection getPoolConn() throws DBPoolIsFullException{ DBPoolConnection poolConnection = new DBPoolConnection(); poolConnection.connNbr=getConnNbr(); if(poolConnection.connNbr==-1){ throw new DBPoolIsFullException("連接池已滿"); }else{ poolConnection.conn=getConn(poolConnection.connNbr); } return poolConnection; } public void freePoolConn(DBPoolConnection poolConnection){ if(poolConnection==null){ System.out.println("poolConnection==null,不需要釋放"); }else{ freeConn(poolConnection.connNbr); } } public void printPoolStatus(){ for(int i=0;i<this.connStatus.length;i++){ System.out.println(""); System.out.print(this.connStatus[i].toString()); if(this.lastQueryTime[i]==null){ System.out.print("-[null] "); }else{ System.out.print("-["+this.lastQueryTime[i].toString()+"] "); } } System.out.println(""); } public String getCls(){ return this.cls; } public String getUrl(){ return this.url; } public String getUsr(){ return this.usr; } int getConnNbr(){ int iConn=-1; for(int i=0;i<this.connCount;i++){ if(this.connStatus[i].equals("Y")){ this.lastQueryTime[i]=new Date(); this.connStatus[i]="N"; iConn=i; break; } } return iConn; } Connection getConn(int i){ return this.connections[i]; } void closeConnForTest(DBPoolConnection poolConnection){ try{ this.connections[poolConnection.connNbr].close(); }catch(SQLException e){ System.out.println(e.getMessage()); } } boolean checkConn(DBPoolConnection poolConnection){ Statement stmt=null; String checkMessage=""; boolean checkResult=true; //檢查連接是否有效 try{ String sql = "select * from dual"; stmt = this.connections[poolConnection.connNbr].createStatement(); stmt.executeQuery(sql);//execute sql stmt.close(); checkMessage = "checkConn:checkMessage:execute sql success"; System.out.println(checkMessage); }catch(Exception e){ checkMessage = e.getMessage(); System.out.println(e.getMessage());//other exceptions if(checkMessage==null){ checkMessage="e.getMessage() is null"; System.out.println(checkMessage); } //采取激進重連的策略,盡量避免業務中斷 if (checkMessage.indexOf("ORA-00942")>=0){ checkResult=true;//不需要重連 }else if(checkMessage.indexOf("does not exist")>=0){ checkResult=true;//不需要重連 }else if(checkMessage.indexOf("Syntax error")>=0){ checkResult=true;//不需要重連 }else{ checkResult=false;//需要重連 } } return checkResult; } boolean resetConn(DBPoolConnection poolConnection){ boolean result=false;//默認不需要重建連接 if(poolConnection==null){ System.out.println("poolConnection==null,不知道您想重設哪個連接"); }else if(poolConnection.connNbr==-1){ System.out.println("poolConnection.connNbr==-1,不知道您想重設哪個連接"); }else{ if(checkConn(poolConnection)==true){ System.out.println("連接有效,不需要重設"); }else{ //重設連接 try{ Class.forName(this.cls);//register class }catch(ClassNotFoundException e){ System.out.println(e.getMessage()); }catch(Exception e){ System.out.println(e.getMessage());//other exceptions } try{ this.connections[poolConnection.connNbr]=DriverManager.getConnection(this.url, this.usr, this.pss); this.connStatus[poolConnection.connNbr]="Y"; System.out.println(poolConnection.connNbr+"連接已重建"); result = true;//告知調用者連接已重建 }catch(SQLException e){ System.out.println(e.getMessage()); }catch(Exception e){ System.out.println(e.getMessage());//other exceptions } } } return result; } void freeConn(int i){ try{ if(i==-1){ System.out.println("i=-1,不需要釋放"); }else{ this.connections[i].commit(); } }catch(SQLException e){ System.out.println(e.getMessage()); }catch(Exception e){ System.out.println(e.getMessage());//other exceptions } this.connStatus[i]="Y"; } }
DBPoolConfiguration.java
package dataWebService; public class DBPoolConfiguration { private String cls; private String url; private String usr; private String pss; private int connCount;//連接數 public String getCls() { return cls; } public void setCls(String cls) { this.cls = cls; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsr() { return usr; } public void setUsr(String usr) { this.usr = usr; } public String getPss() { return pss; } public void setPss(String pss) { this.pss = pss; } public int getConnCount() { return connCount; } public void setConnCount(int connCount) { this.connCount = connCount; } }
DBPoolConnection.java:
package dataWebService; import java.sql.Connection; public class DBPoolConnection{ public int connNbr=-1; public Connection conn=null; DBPoolConnection(){ this.connNbr=-1; this.conn = null; } }
DBPoolIsFullException.java
package dataWebService; public class DBPoolIsFullException extends Exception{ static final long serialVersionUID=1L; DBPoolIsFullException(String message){ super(message); } }
Test.java
package myAction; import dataWebService.DBPool; import dataWebService.DBPoolConnection; import dataWebService.DBPoolConfiguration; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; public class Test { static String rpad(String str,int len){ String s = str; if(s==null){ s=""; } while(s.getBytes().length<len){ s += " "; } return s; } public static void main(String[] args) { //初始化 String cls ="",url="",usr="",pss="",sql=""; Statement stmt=null; ResultSet rs=null; String gapStr="|";//分隔符 int connCount=2;//最大連接數 //連接Oracle 配置 cls = "oracle.jdbc.driver.OracleDriver"; url = "jdbc:oracle:thin:@localhost:1521:myoradb"; usr = "abc"; pss = "123"; sql = "select t.field_id,t.field_content,to_char(t.record_create_date,'YYYYMMDD') day from my_table t"; DBPoolConfiguration poolConfiguration=new DBPoolConfiguration(); poolConfiguration.setCls(cls); poolConfiguration.setUrl(url); poolConfiguration.setUsr(usr); poolConfiguration.setPss(pss); poolConfiguration.setConnCount(connCount); DBPool myPool = new DBPool(poolConfiguration); myPool.initPool(); System.out.println(""); System.out.print("after init Pool"); myPool.printPoolStatus(); System.out.println(""); DBPoolConnection c1=null; try{ c1 = myPool.getPoolConn(); System.out.println(""); System.out.print("after getPoolConn"); myPool.printPoolStatus(); System.out.println(""); stmt = c1.conn.createStatement(); rs = stmt.executeQuery(sql); ResultSetMetaData md = rs.getMetaData(); String recordHead = ""; int colCount = md.getColumnCount(); for(int i=1;i<=colCount;i++){ if(recordHead.equals("")){ recordHead += rpad(md.getColumnName(i),md.getColumnDisplaySize(i)); }else{ recordHead+= gapStr + rpad(md.getColumnName(i),md.getColumnDisplaySize(i)); } } System.out.println(recordHead);//打印表頭 while(rs.next()){ String tmp = ""; for(int i=1;i<=colCount;i++){ int colSize = md.getColumnDisplaySize(i)>md.getColumnName(i).length()?md.getColumnDisplaySize(i):md.getColumnName(i).length(); if(tmp.equals("")){ tmp += rpad(rs.getString(i),colSize); }else{ tmp += gapStr + rpad(rs.getString(i),colSize); } } System.out.println(tmp);//打印數據 } stmt.close();//釋放資源但是不關閉連接 myPool.freePoolConn(c1); System.out.println(""); System.out.print("after freePoolConn"); myPool.printPoolStatus(); System.out.println(""); }catch(Exception e){ System.out.println(e.getMessage()); } myPool.freePool(); }//end of main }
下面是Test.java的執行結果:
initPool is ready... after init Pool Y-[null] Y-[null] after getPoolConn N-[Wed Mar 20 14:46:31 GMT 2013] Y-[null] FIELD_ID|FIELD_CONTENT |DAY 001 |this is first record |20130320 002 |this is second record |20130320 003 |this is third record |20130320 after freePoolConn Y-[Wed Mar 20 14:46:31 GMT 2013] Y-[null] freePool is over ...
感謝您的耐心讀到此處,我相信接下去的文字會更有價值。
保持連接池簡單性的幾個設計思想(不一定正確):
1)在系統中連接池不應作為一個獨立的模塊,最好是作為某模塊的底層實現。這樣可以將超時管理、請求隊列、確保資源釋放、數據分頁(方言不可避免了)等功能剝離出去。
2)固定連接數比動態連接數容易實現,簡單的代碼更易於維護。
本例有待完善之處(供參考):
1)查詢空閒連接要遍歷數組,這樣當池中的連接數很大時可能會有問題(具體沒測過)
2)可以添加連接池阻塞的功能,有時候後台數據庫需要重啟,或者想重建連接池以提供更多的並發連接數?