站在JAVA岛上看海

天行键,君子以自强不息;地势坤,君子以厚德载物!

用户操作
[留言]  [发消息]  [加为好友] 
订阅我的博客
XML聚合    FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
zhoubin_java的公告
1、有的文章是从网上down来的,我再发上来是本着资源共享的原则,望原创作者谅解。如果有侵权行为,请留言给我,我会及时撤走文章!<br>2、希望你能够在我的博客得到你想到的东西,如果没有找到,请留言给我,我会尽力帮你,并尽快发上来!<BR> <CENTER><!--START TODAY--> <SCRIPT language=JavaScript> var bsYear; var bsDate; var bsWeek; var arrLen=8; //数组长度 var sValue=0; //当年的秒数 var dayiy=0; //当年第几天 var miy=0; //月份的下标 var iyear=0; //年份标记 var dayim=0; //当月第几天 var spd=86400; //每天的秒数 var year1999="30;29;29;30;29;29;30;29;30;30;30;29"; //354 var year2000="30;30;29;29;30;29;29;30;29;30;30;29"; //354 var year2001="30;30;29;30;29;30;29;29;30;29;30;29;30"; //384 var year2002="30;30;29;30;29;30;29;29;30;29;30;29"; //354 var year2003="30;30;29;30;30;29;30;29;29;30;29;30"; //355 var year2004="29;30;29;30;30;29;30;29;30;29;30;29;30"; //384 var year2005="29;30;29;30;29;30;30;29;30;29;30;29"; //354 var year2006="30;29;30;29;30;30;29;29;30;30;29;29;30"; var month1999="正月;二月;三月;四月;五月;六月;七月;八月;九月;十月;十一月;十二月" var month2001="正月;二月;三月;四月;闰四月;五月;六月;七月;八月;九月;十月;十一月;十二月" var month2004="正月;二月;闰二月;三月;四月;五月;六月;七月;八月;九月;十月;十一月;十二月" var month2006="正月;二月;三月;四月;五月;六月;七月;闰七月;八月;九月;十月;十一月;十二月" var Dn="初一;初二;初三;初四;初五;初六;初七;初八;初九;初十;十一;十二;十三;十四;十五;十六;十七;十八;十九;二十;廿一;廿二;廿三;廿四;廿五;廿六;廿七;廿八;廿九;三十"; var Ys=new Array(arrLen); Ys[0]=919094400;Ys[1]=949680000;Ys[2]=980265600; Ys[3]=1013443200;Ys[4]=1044028800;Ys[5]=1074700800; Ys[6]=1107878400;Ys[7]=1138464000; var Yn=new Array(arrLen); //农历年的名称 Yn[0]="己卯年";Yn[1]="庚辰年";Yn[2]="辛巳年"; Yn[3]="壬午年";Yn[4]="癸未年";Yn[5]="甲申年"; Yn[6]="乙酉年";Yn[7]="丙戌年"; var D=new Date(); var yy=D.getYear(); var mm=D.getMonth()+1; var dd=D.getDate(); var ww=D.getDay(); if (ww==0) ww="<font color=RED>星期日</font>"; if (ww==1) ww="星期一"; if (ww==2) ww="星期二"; if (ww==3) ww="星期三"; if (ww==4) ww="星期四"; if (ww==5) ww="星期五"; if (ww==6) ww="<font color=green>星期六</font>"; ww=ww; var ss=parseInt(D.getTime() / 1000); if (yy<100) yy="19"+yy; for (i=0;i<arrLen;i++) if (ss>=Ys[i]){ iyear=i; sValue=ss-Ys[i]; //当年的秒数 } dayiy=parseInt(sValue/spd)+1; //当年的天数 var dpm=year1999; if (iyear==1) dpm=year2000; if (iyear==2) dpm=year2001; if (iyear==3) dpm=year2002; if (iyear==4) dpm=year2003; if (iyear==5) dpm=year2004; if (iyear==6) dpm=year2005; if (iyear==7) dpm=year2006; dpm=dpm.split(";"); var Mn=month1999; if (iyear==2) Mn=month2001; if (iyear==5) Mn=month2004; if (iyear==7) Mn=month2006; Mn=Mn.split(";"); var Dn="初一;初二;初三;初四;初五;初六;初七;初八;初九;初十;十一;十二;十三;十四;十五;十六;十七;十八;十九;二十;廿一;廿二;廿三;廿四;廿五;廿六;廿七;廿八;廿九;三十"; Dn=Dn.split(";"); dayim=dayiy; var total=new Array(13); total[0]=parseInt(dpm[0]); for (i=1;i<dpm.length-1;i++) total[i]=parseInt(dpm[i])+total[i-1]; for (i=dpm.length-1;i>0;i--) if (dayim>total[i-1]){ dayim=dayim-total[i-1]; miy=i; } bsWeek=ww; bsDate=yy+"年"+mm+"月"; bsDate2=dd; bsYear="农历"+Yn[iyear]; bsYear2=Mn[miy]+Dn[dayim-1]; if (ss>=Ys[7]||ss<Ys[0]) bsYear=Yn[7]; function time(){ document.write("<table border='0' style='font-size: 9pt; font-family:Tahoma;background:infobackground' cellspacing='0' width='90' bordercolor='red' height='110' cellpadding='0'"); document.write("<tr><td align='center' style='border: 1 solid #FFCC66;padding-top:4px'><b><font style='font-family: Verdana;color:#0979C4'>"+bsDate+"</font><br><div style='font-family: Arial Black;font-size:18pt;color:#FF8040'>"+bsDate2+"</div><div style='FONT-SIZE: 10.5pt;color:#000000'>"); document.write(bsWeek+"</div>"+"</b><font color=#9B4E00>"); document.write(bsYear+"<br>"+bsYear2+"</td></tr></table>"); } </SCRIPT> <DIV id=clock style="RIGHT: 10px; TOP: 5px"> <SCRIPT language=JavaScript>time()</SCRIPT> </DIV><BR><!--END TODAY--></CENTER><BR><EMBED src=http://bbs.smgbb.cn/Skins/Default/clock.swf width=150 height=150 menu="false" loop="true" quality="high" wmode="transparent"><BR><EMBED src=http://www.chinasunbelt.com/clock.swf width=150 height=150 menu="false" loop="true" quality="high" wmode="transparent"></CENTER><BR> <CENTER><B>我的联系方式:</B></CENTER><IMG height=20 src="http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_QQ.gif" width=20>38603688<BR> <P><IMG height=20 src="http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_email.jpg" width=20><a href="mailto:mail.zhoubin@163.com">mail.zhoubin@163.com</a></P><BR> <TABLE height=179 cellSpacing=1 width=40 background=http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_star.gif border=0> <TBODY> <TR> <TD width=179 height=5> <P align=center><IMG height=56 src="http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_welcome.gif" width=164 border=0></P></TD></TR> <TR> <TD width=179 height=99><IMG height=149 src="http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_moon.gif" width=167 border=0></TD></TR> <TR> <TD width=179 height=71><IMG height=93 src="http://blog.csdn.net/images/blog_csdn_net/shaohui/60135/o_huanyinglai.gif" width=154 border=0></TD></TR></TBODY></TABLE><BR> <SCRIPT>var s="http://www.google.com/search?hl=zh-CN&lr=lang_zh-CN&q=";</SCRIPT> <DIV align=center><IMG src="http://blog.csdn.net/images/blog_csdn_net/ylfly/37912/o_google's_logo.gif" border=0> <INPUT size=16 name=search> <INPUT onclick=window.open(s+document.all.search.value) type=button value=搜索 name=google></DIV></SCRIPT><BR><!-- Blog日历表开始 --> <SCRIPT language=javascript> var diarydays="<2004-08-05><2004-08-09><2004-08-12><2004-08-17><2004-08-19><2004-07-14><2004-07-13><2004-07-08><2004-07-05><2004-07-04><2004-07-03><2004-06-27><2004-06-26><2004-06-25>"; </SCRIPT> <STYLE>.calendarBigBorder { BORDER-RIGHT: #999 1px dotted; BORDER-TOP: #999 1px dotted; FONT-SIZE: 9pt; BORDER-LEFT: #999 1px dotted; WIDTH: 170px; BORDER-BOTTOM: #999 1px dotted; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; BACKGROUND-COLOR: #ffffff; TEXT-DECORATION: none } .calendarTd { FONT-SIZE: 9pt; WIDTH: 11%; COLOR: #000000; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 18px; BACKGROUND-COLOR: #eeeeee; TEXT-ALIGN: center } .calendarMInput { BORDER-RIGHT: #666666 1px solid; BORDER-TOP: #666666 1px solid; FONT-SIZE: 9pt; BORDER-LEFT: #666666 1px solid; WIDTH: 19px; COLOR: #0099ff; BORDER-BOTTOM: #666666 1px solid; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 15px; BACKGROUND-COLOR: #ffffff; TEXT-DECORATION: none } .calendarYInput { BORDER-RIGHT: #666666 1px solid; BORDER-TOP: #666666 1px solid; FONT-SIZE: 9pt; BORDER-LEFT: #666666 1px solid; WIDTH: 34px; COLOR: #0099ff; BORDER-BOTTOM: #666666 1px solid; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 15px; BACKGROUND-COLOR: #ffffff; TEXT-DECORATION: none } .calendarMonthTitle { BORDER-TOP-WIDTH: 1px; FONT-WEIGHT: normal; BORDER-LEFT-WIDTH: 1px; FONT-SIZE: 9pt; BORDER-LEFT-COLOR: #999999; COLOR: #333333; BORDER-TOP-COLOR: #999999; BORDER-BOTTOM: #999999 1px; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 24px; BACKGROUND-COLOR: #eeeeee; TEXT-ALIGN: center; BORDER-RIGHT-WIDTH: 1px; TEXT-DECORATION: none; BORDER-RIGHT-COLOR: #999999 } .calendarNow { FONT-WEIGHT: bold; FONT-SIZE: 9pt; COLOR: #000000; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 18px; BACKGROUND-COLOR: #c0c9d3; TEXT-ALIGN: center } .calendarDaySat { FONT-SIZE: 9pt; WIDTH: 12%; COLOR: #333333; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 18px; BACKGROUND-COLOR: #eeeeee; TEXT-ALIGN: center; TEXT-DECORATION: none } .calendarDaySun { FONT-SIZE: 9pt; WIDTH: 12%; COLOR: #333333; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 18px; BACKGROUND-COLOR: #eeeeee; TEXT-ALIGN: center; TEXT-DECORATION: none } .calendarLink { FONT-WEIGHT: normal; FONT-SIZE: 9pt; COLOR: #333333; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; HEIGHT: 18px; BACKGROUND-COLOR: #e7e7f6; TEXT-ALIGN: center; TEXT-DECORATION: none } .categoryTxt { FONT-SIZE: 9pt; COLOR: #333333; LINE-HEIGHT: 23px; FONT-FAMILY: "Arial", "Helvetica", "sans-serif", "宋体"; TEXT-DECORATION: none } .categoryTable { BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #71b99c 1px solid; BORDER-BOTTOM: #000000 1px solid; BORDER-LEFT-STYLE: none } </STYLE> <SCRIPT language=JavaScript> var months = new Array("一", "二", "三","四", "五", "六", "七", "八", "九","十", "十一", "十二"); var daysInMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31,30, 31, 30, 31); var days = new Array("日","一", "二", "三","四", "五", "六"); var classTemp; var calendarHover="calendarHover"; var today=new getToday(); var year=today.year; var month=today.month; var newCal; //得到某月天数 function getDays(month, year) { if (1 == month) return ((0 == year % 4) && (0 != (year % 100))) ||(0 == year % 400) ? 29 : 28; else return daysInMonth[month]; } //得到当天时间信息 function getToday() { this.now = new Date(); this.year = this.now.getFullYear(); this.month = this.now.getMonth(); this.day = this.now.getDate(); } //生成日历 function Calendar() { newCal = new Date(year,month,1); //当前月的第一天 today = new getToday(); var day = -1; //用来判断日历中是否为当天 var startDay = newCal.getDay(); //当月开始时间 var endDay=getDays(newCal.getMonth(), newCal.getFullYear());//当月结束时间 var daily = 0; //用来生成日历中的天数值 if ((today.year == newCal.getFullYear()) &&(today.month == newCal.getMonth())) day = today.day; var caltable = document.all.caltable.tBodies.calendar; //得到日历表格的集合 var intDaysInMonth =getDays(newCal.getMonth(), newCal.getFullYear()); //得到当月天数 //生成日历 for (var intWeek = 0; intWeek < caltable.rows.length;intWeek++) for (var intDay = 0;intDay < caltable.rows[intWeek].cells.length;intDay++) { var cell = caltable.rows[intWeek].cells[intDay]; //得到单元袼 //生成字符串用于判断当天是否有日志 var montemp=(newCal.getMonth()+1)<10?("0"+(newCal.getMonth()+1)):(newCal.getMonth()+1); if ((intDay == startDay) && (0 == daily)){ daily = 1;} var daytemp=daily<10?("0"+daily):(daily); var d="<"+newCal.getFullYear()+"-"+montemp+"-"+daytemp+">"; //选择样式 if(day==daily) cell.className="calendarNow"; else if(diarydays.indexOf(d)!=-1) cell.className="calendarLink"; else if(intDay==6) cell.className = "calendarDaySat"; else if (intDay==0) cell.className ="calendarDaySun"; else cell.className="calendarTd"; //生成值 if ((daily > 0) && (daily <= intDaysInMonth)) { cell.innerText = daily; daily++; } else cell.innerText = ""; } document.all.year.value=year; document.all.month.value=month+1; } function subMonth() { if ((month-1)<0) { month=11; year=year-1; } else { month=month-1; } Calendar(); } function addMonth() { if((month+1)>11) { month=0; year=year+1; } else { month=month+1; } Calendar(); } //得到响应事件 function getDiary() { var mon=(newCal.getMonth()+1)<10?("0"+(newCal.getMonth()+1)):(newCal.getMonth()+1); var day=event.srcElement.innerText<10?("0"+event.srcElement.innerText):(event.srcElement.innerText); var d="<"+newCal.getFullYear()+"-"+mon+"-"+day+">"; if ("TD" == event.srcElement.tagName) if (("" != event.srcElement.innerText)&&(diarydays.indexOf(d)!=-1)) { diary.location="/foxmail/archive/"+newCal.getFullYear()+"/"+mon+"/"+day+".aspx"; window.location.href = diary.location; } } function setDate() { if (document.all.month.value<1||document.all.month.value>12) { alert("月的有效范围在1-12之间!"); return; } year=Math.ceil(document.all.year.value); month=Math.ceil(document.all.month.value-1); Calendar(); } </SCRIPT> <TABLE class=calendarBigBorder id=caltable cellSpacing=1 cellPadding=0 width=200 border=0> <THEAD> <TR vAlign=center align=middle> <TD class=calendarMonthTitle colSpan=7><INPUT class=calendarYInput onpaste="this.value=this.value.replace(/[^0-9]/g,'')" onkeydown="if (event.keyCode==13){setDate()}" onkeyup="this.value=this.value.replace(/[^0-9]/g,'')" maxLength=4 size=3 name=year>年 <INPUT class=calendarMInput onpaste="this.value=this.value.replace(/[^0-9]/g,'')" onkeydown="if (event.keyCode==13){setDate()}" onkeyup="this.value=this.value.replace(/[^0-9]/g,'')" maxLength=2 size=1 name=month>月 [<A onclick=addMonth() href="javascript:;">下月</A>] [<A onclick=subMonth() href="javascript:;">上月</A>]</TD></TR> <TR vAlign=center align=middle> <SCRIPT language=JavaScript> document.write("<TD class=calendarDaySun id=diary>" + days[0] + "</TD>"); for (var intLoop = 1; intLoop < days.length-1; intLoop++) document.write("<TD class=calendarTd id=diary>" + days[intLoop] + "</TD>"); document.write("<TD class=calendarDaySat id=diary>" + days[intLoop] + "</TD>"); </SCRIPT> </TR></THEAD> <TBODY onmouseup=document.selection.empty() oncontextmenu="return false" onselectstart="return false" id=calendar ondragstart="return false" onbeforecopy="return false" onclick=getDiary() oncopy=document.selection.empty() align=middle onselect="document.selection.empty()" cellpadding="0" cellspacing="0" border="1"> <SCRIPT language=JavaScript> for (var intWeeks = 0; intWeeks < 6; intWeeks++) { document.write("<TR style='cursor:hand'>"); for (var intDays = 0; intDays < days.length;intDays++) document.write("<TD class=calendarTd onMouseover='{classTemp=this.className;this.className=calendarHover}' onMouseOut='this.className=classTemp'></TD>"); document.write("</TR>"); } </SCRIPT> </TBODY></TABLE> <SCRIPT language=JavaScript> Calendar(); </SCRIPT> <!-- Blog日历表结束 --><BR> <IFRAME border=0 align=center marginWidth=0 marginHeight=0 src='http://appnews.qq.com/cgi-bin/news_qq_search?city=' frameBorder=0 width=157 scrolling=no height=240 allowTransparency></IFRAME><br>
文章分类
JAVA编程
JAVA中文站(RSS)
SUN中国技术社区(RSS)
中国大学生JAVA/Solaris协会(RSS)
JSP编程
中国JSP技术网站(RSS)
常用网站
黑咖啡屋(RSS)
软件下载
天地无忧论坛(RSS)
注册码搜索(RSS)
软件万花筒(RSS)
音乐在线
音乐视听2000
存档

原创  使用JAVA中的动态代理实现数据库连接池 收藏

 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的。但是另外一个共同的问题是,它们同时不允许使用者显式的调用Connection.close()方法,而需要用其规定的一个方法来关闭连接。这种做法有两个缺点:

  第一:改变了用户使用习惯,增加了用户的使用难度。

  首先我们来看看一个正常的数据库操作过程:

int executeSQL(String sql) throws SQLException
{
 Connection conn = getConnection(); //通过某种方式获取数据库连接
 PreparedStatement ps = null;
 int res = 0;
 try{
  ps = conn.prepareStatement(sql);
  res = ps.executeUpdate();
}finally{
try{
ps.close();
}catch(Exception e){}
try{
 conn.close();//
}catch(Exception e){}
}
return res;
}

  使用者在用完数据库连接后通常是直接调用连接的方法close来释放数据库资源,如果用我们前面提到的连接池的实现方法,那语句conn.close()将被某些特定的语句所替代。

  第二:使连接池无法对之中的所有连接进行独占控制。由于连接池不允许用户直接调用连接的close方法,一旦使用者在使用的过程中由于习惯问题直接关闭了数据库连接,那么连接池将无法正常维护所有连接的状态,考虑连接池和应用由不同开发人员实现时这种问题更容易出现。

  综合上面提到的两个问题,我们来讨论一下如何解决这两个要命的问题。

  首先我们先设身处地的考虑一下用户是想怎么样来使用这个数据库连接池的。用户可以通过特定的方法来获取数据库的连接,同时这个连接的类型应该是标准的java.sql.Connection。用户在获取到这个数据库连接后可以对这个连接进行任意的操作,包括关闭连接等。

  通过对用户使用的描述,怎样可以接管Connection.close方法就成了我们这篇文章的主题。

  为了接管数据库连接的close方法,我们应该有一种类似于钩子的机制。例如在Windows编程中我们可以利用Hook API来实现对某个Windows API的接管。在JAVA中同样也有这样一个机制。JAVA提供了一个Proxy类和一个InvocationHandler,这两个类都在java.lang.reflect包中。我们先来看看SUN公司提供的文档是怎么描述这两个类的。

public interface InvocationHandler

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance,
the method invocation is encoded and dispatched to the invoke method of its invocation handler.

  SUN的API文档中关于Proxy的描述很多,这里就不罗列出来。通过文档对接口InvocationHandler的描述我们可以看到当调用一个Proxy实例的方法时会触发Invocationhanlder的invoke方法。从JAVA的文档中我们也同时了解到这种动态代理机制只能接管接口的方法,而对一般的类无效,考虑到java.sql.Connection本身也是一个接口由此就找到了解决如何接管close方法的出路。

  首先,我们先定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数,具体定义如下:

public class ConnectionParam implements Serializable
{
 private String driver;    //数据库驱动程序
 private String url;     //数据连接的URL
 private String user;     //数据库用户名
 private String password;    //数据库密码
 private int minConnection = 0;  //初始化连接数
 private int maxConnection = 50;  //最大连接数
 private long timeoutValue = 600000;//连接的最大空闲时间
 private long waitTime = 30000;  //取连接的时候如果没有可用连接最大的等待时间

  其次是连接池的工厂类ConnectionFactory,通过该类来将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,具体代码如下:

/**
 * 连接池类厂,该类常用来保存多个数据源名称合数据库连接池对应的哈希
 * @author liusoft
 */
public class ConnectionFactory
{
 //该哈希表用来保存数据源名和连接池对象的关系表
 static Hashtable connectionPools = null;
 static{
  connectionPools = new Hashtable(2,0.75F);
 }
 /**
  * 从连接池工厂中获取指定名称对应的连接池对象
  * @param dataSource 连接池对象对应的名称
  * @return DataSource 返回名称对应的连接池对象
  * @throws NameNotFoundException 无法找到指定的连接池
  */
 public static DataSource lookup(String dataSource)
  throws NameNotFoundException
 {
  Object ds = null;
  ds = connectionPools.get(dataSource);
  if(ds == null || !(ds instanceof DataSource))
   throw new NameNotFoundException(dataSource);
  return (DataSource)ds;
 }

 /**
  * 将指定的名字和数据库连接配置绑定在一起并初始化数据库连接池
  * @param name  对应连接池的名称
  * @param param 连接池的配置参数,具体请见类ConnectionParam
  * @return DataSource 如果绑定成功后返回连接池对象
  * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
  * @throws ClassNotFoundException  无法找到连接池的配置中的驱动程序类
  * @throws IllegalAccessException  连接池配置中的驱动程序类有误
  * @throws InstantiationException  无法实例化驱动程序类
  * @throws SQLException    无法正常连接指定的数据库
  */
 public static DataSource bind(String name, ConnectionParam param)
  throws NameAlreadyBoundException,ClassNotFoundException,
    IllegalAccessException,InstantiationException,SQLException
 {
  DataSourceImpl source = null;
  try{
   lookup(name);
   throw new NameAlreadyBoundException(name);
  }catch(NameNotFoundException e){
   source = new DataSourceImpl(param);
   source.initConnection();
   connectionPools.put(name, source);
  }
  return source;
 }
 /**
  * 重新绑定数据库连接池
  * @param name  对应连接池的名称
  * @param param 连接池的配置参数,具体请见类ConnectionParam
  * @return DataSource 如果绑定成功后返回连接池对象
  * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
  * @throws ClassNotFoundException  无法找到连接池的配置中的驱动程序类
  * @throws IllegalAccessException  连接池配置中的驱动程序类有误
  * @throws InstantiationException  无法实例化驱动程序类
  * @throws SQLException    无法正常连接指定的数据库
  */
 public static DataSource rebind(String name, ConnectionParam param)
  throws NameAlreadyBoundException,ClassNotFoundException,
    IllegalAccessException,InstantiationException,SQLException
 {
  try{
   unbind(name);
  }catch(Exception e){}
  return bind(name, param);
 }
 /**
  * 删除一个数据库连接池对象
  * @param name
  * @throws NameNotFoundException
  */
 public static void unbind(String name) throws NameNotFoundException
 {
  DataSource dataSource = lookup(name);
  if(dataSource instanceof DataSourceImpl){
   DataSourceImpl dsi = (DataSourceImpl)dataSource;
   try{
    dsi.stop();
    dsi.close();
   }catch(Exception e){
   }finally{
    dsi = null;
   }
  }
  connectionPools.remove(name);
 }
 
}

  ConnectionFactory主要提供了用户将将连接池绑定到一个具体的名称上以及取消绑定的操作。使用者只需要关心这两个类即可使用数据库连接池的功能。下面我们给出一段如何使用连接池的代码:

 String name = "pool";
 String driver = " sun.jdbc.odbc.JdbcOdbcDriver ";
 String url = "jdbc:odbc:datasource";
 ConnectionParam param = new ConnectionParam(driver,url,null,null);
 param.setMinConnection(1);
 param.setMaxConnection(5);
 param.setTimeoutValue(20000);
 ConnectionFactory.bind(name, param);
 System.out.println("bind datasource ok.");
 //以上代码是用来登记一个连接池对象,该操作可以在程序初始化只做一次即可
 //以下开始就是使用者真正需要写的代码
 DataSource ds = ConnectionFactory.lookup(name);
 try{
  for(int i=0;i<10;i++){
   Connection conn = ds.getConnection();
   try{
    testSQL(conn, sql);
   }finally{
    try{
     conn.close();
    }catch(Exception e){}
   }
  }
 }catch(Exception e){
  e.printStackTrace();
 }finally{
  ConnectionFactory.unbind(name);
  System.out.println("unbind datasource ok.");
  System.exit(0);
 }

  从使用者的示例代码就可以看出,我们已经解决了常规连接池产生的两个问题。但是我们最最关心的是如何解决接管close方法的办法。接管工作主要在ConnectionFactory中的两句代码:

source = new DataSourceImpl(param);
source.initConnection();

  DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection

/**
 * @see javax.sql.DataSource#getConnection(String,String)
 */
 public Connection getConnection(String user, String password) throws SQLException
 {
  //首先从连接池中找出空闲的对象
  Connection conn = getFreeConnection(0);
  if(conn == null){
   //判断是否超过最大连接数,如果超过最大连接数
   //则等待一定时间查看是否有空闲连接,否则抛出异常告诉用户无可用连接
   if(getConnectionCount() >= connParam.getMaxConnection())
    conn = getFreeConnection(connParam.getWaitTime());
   else{//没有超过连接数,重新获取一个数据库的连接
    connParam.setUser(user);
    connParam.setPassword(password);
    Connection conn2 = DriverManager.getConnection(connParam.getUrl(),
    user, password);
    //代理将要返回的连接对象
    _Connection _conn = new _Connection(conn2,true);
    synchronized(conns){
     conns.add(_conn);
    }
    conn = _conn.getConnection();
   }
  }
  return conn;
 }
 /**
  * 从连接池中取一个空闲的连接
  * @param nTimeout 如果该参数值为0则没有连接时只是返回一个null
  * 否则的话等待nTimeout毫秒看是否还有空闲连接,如果没有抛出异常
  * @return Connection
  * @throws SQLException
  */
 protected synchronized Connection getFreeConnection(long nTimeout)
  throws SQLException
 {
  Connection conn = null;
  Iterator iter = conns.iterator();
  while(iter.hasNext()){
   _Connection _conn = (_Connection)iter.next();
   if(!_conn.isInUse()){
    conn = _conn.getConnection();
    _conn.setInUse(true);    
    break;
   }
  }
  if(conn == null && nTimeout > 0){
   //等待nTimeout毫秒以便看是否有空闲连接
   try{
    Thread.sleep(nTimeout);
   }catch(Exception e){}
   conn = getFreeConnection(0);
   if(conn == null)
    throw new SQLException("没有可用的数据库连接");
  }
  return conn;
 }

  DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。

  终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。

/**
 * 数据连接的自封装,屏蔽了close方法
 * @author Liudong
 */
class _Connection implements InvocationHandler
{
 private final static String CLOSE_METHOD_NAME = "close";
 private Connection conn = null;
 //数据库的忙状态
 private boolean inUse = false;
 //用户最后一次访问该连接方法的时间
 private long lastAccessTime = System.currentTimeMillis();
 
 _Connection(Connection conn, boolean inUse){
  this.conn = conn;
  this.inUse = inUse;
 }
 /**
  * Returns the conn.
  * @return Connection
  */
 public Connection getConnection() {
  //返回数据库连接conn的接管类,以便截住close方法
  Connection conn2 = (Connection)Proxy.newProxyInstance(
   conn.getClass().getClassLoader(),
   conn.getClass().getInterfaces(),this);
  return conn2;
 }
 /**
  * 该方法真正的关闭了数据库的连接
  * @throws SQLException
  */
 void close() throws SQLException{
  //由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接
  conn.close();
 }
 /**
  * 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;
  //判断是否调用了close的方法,如果调用close方法则把连接置为无用状态
  if(CLOSE_METHOD_NAME.equals(m.getName()))
   setInUse(false);  
  else
   obj = m.invoke(conn, args); 
  //设置最后一次访问时间,以便及时清除超时的连接
  lastAccessTime = System.currentTimeMillis();
  return obj;
 }
  
 /**
  * Returns the lastAccessTime.
  * @return long
  */
 public long getLastAccessTime() {
  return lastAccessTime;
 }

 /**
  * Sets the inUse.
  * @param inUse The inUse to set
  */
 public void setInUse(boolean inUse) {
  this.inUse = inUse;
 }
}

  一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:

 /**
  * 关闭该连接池中的所有数据库连接
  * @return int 返回被关闭连接的个数
  * @throws SQLException
  */
 public int close() throws SQLException
 {
  int cc = 0;
  SQLException excp = null;
  Iterator iter = conns.iterator();
  while(iter.hasNext()){
   try{
    ((_Connection)iter.next()).close();
    cc ++;
   }catch(Exception e){
    if(e instanceof SQLException)
     excp = (SQLException)e;
   }
  }
  if(excp != null)
   throw excp;
  return cc;
 }

  该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。

  以上文字只是描述了接口方法的接管,具体一个实用的连接池模块还需要对空闲连接的监控并及时释放连接。

发表于 @ 2005年12月03日 17:39:00 | 评论( loading... ) | 编辑| 举报| 收藏

旧一篇:关于数据库连接池 | 新一篇:response.sendRedirect的一点实际用法

  • 发表评论
  • 评论内容:
  •  
Copyright © zhoubin_java
Powered by CSDN Blog