dbcp代码研读以及就数据库连接失效的解决

问题

网上很多评论说dbcp有很多bug,但是都没有指明是什么bug,只有一部分人说数据库如果因为某种原因断掉后再dbcp取道的连接都是失效的连接,而没有重新取。就此研读了一下dbcp的代码,共享之。

分析

dbcp使用apache的对象池objectpool作为连接池的实现,有以下主要的方法

object borrowobject() throws exception;从对象池取得一个有效对象

void returnobject(object obj) throws exception;使用完的对象放回对象池

void invalidateobject(object obj) throws exception;使对象失效

void addobject() throws exception;生成一个新对象


objectpool的一个实现就是genericobjectpool,这个类使用对象工厂poolableobjectfactory实现对象的生成,失效检查等等功能,以其实现数据库连接工厂poolableconnectionfactory做以说明,主要方法:

     object makeobject() throws exception; 使用connectionfactory生成新连接

     void destroyobject(object obj) throws exception;关闭连接

     boolean validateobject(object obj); 验证连接是否有效,如果_validationquery不空,则使用该属性作为验证连接是否有效的sql语句,查询数据库

     void activateobject(object obj) throws exception;激活连接对象

     void passivateobject(object obj) throws exception; 关闭连接生成过的statement和resultset,使连接处于非活动状态



而genericobjectpool有几个主要属性

     _timebetweenevictionrunsmillis:失效检查线程运行时间间隔,默认-1

     _maxidle:对象池中对象最大个数

     _minidle:对象池中对象最小个数

     _maxactive:可以从对象池中取出的对象最大个数,为0则表示没有限制,默认为8

在构造genericobjectpool时,会生成一个内嵌类evictor,实现自runnable接口。如果_timebetweenevictionrunsmillis大于0,每过_timebetweenevictionrunsmillis毫秒evictor会调用evict()方法,检查对象的闲置时间是否大于 _minevictableidletimemillis毫秒(_minevictableidletimemillis小于等于0时则忽略,默认为30分钟),是则销毁此对象,否则就激活并校验对象,然后调用ensureminidle方法检查确保池中对象个数不小于_minidle。在调用returnobject方法把对象放回对象池,首先检查该对象是否有效,然后调用poolableobjectfactory 的passivateobject方法使对象处于非活动状态。再检查对象池中对象个数是否小于_maxidle,是则可以把此对象放回对象池,否则销毁此对象

还有几个很重要的属性,_testonborrow、_testonreturn、_testwhileidle,这些属性的意义是取得、返回对象和空闲时是否进行验证,检查对象是否有效,默认都为false即不验证。所以当使用dbcp时,数据库连接因为某种原因断掉后,再从连接池中取得连接又不进行验证,这时取得的连接实际已经时无效的数据库连接了。网上很多说dbcp的bug应该都是如此吧,只有把这些属性设为true,再提供_validationquery语句就可以保证数据库连接始终有效了oracle数据库可以使用select count(*) from dual,不过dbcp要求_validationquery语句查询的记录集必须不为空,可能这也可以算一个小小的bug,其实只要_validationquery语句执行通过就可以了。

注意事项

所以使用dbcp连接池放必须注意构造genericobjectpool对象时

     validationquery:select count(*) from dual

       _testonborrow、_testonreturn、_testwhileidle:最好都设为true

       _minevictableidletimemillis:大于0 ,进行连接空闲时间判断,或为0,对空闲的连接不进行验证

     _timebetweenevictionrunsmillis:失效检查线程运行时间间隔,如果小于等于0,不会启动检查线程

GenericObjectPool
GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是:

可以设定最多能从池中借出多少个对象。
可以设定池中最多能保存多少个对象。
可以设定在池中已无对象可借的情况下,调用它的borrowObject方法时的行为,是等待、创建新的实例还是抛出异常。
可以分别设定对象借出和还回时,是否进行有效性检查。
可以设定是否使用一个单独的线程,对池内对象进行后台清理。
GenericObjectPool的构造方法共有七个,其中:

最简单的一个是GenericObjectPool(PoolableObjectFactory factory)。仅仅指明要用的PoolableObjectFactory实例,其它参数则采用默认值。
最复杂的一个是GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle)。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例。
参数maxActive指明能从池中借出的对象的最大数目。如果这个值不是正数,表示没有限制。
参数whenExhaustedAction指定在池中借出对象的数目已达极限的情况下,调用它的borrowObject方法时的行为。可以选用的值有:
GenericObjectPool.WHEN_EXHAUSTED_BLOCK,表示等待;
GenericObjectPool.WHEN_EXHAUSTED_GROW,表示创建新的实例(不过这就使maxActive参数失去了意义);
GenericObjectPool.WHEN_EXHAUSTED_FAIL,表示抛出一个java.util.NoSuchElementException异常。
参数maxWait指明若在对象池空时调用borrowObject方法的行为被设定成等待,最多等待多少毫秒。如果等待时间超过了这个数值,则会抛出一个java.util.NoSuchElementException异常。如果这个值不是正数,表示无限期等待。
参数testOnBorrow设定在借出对象时是否进行有效性检查。
参数testOnBorrow设定在还回对象时是否进行有效性检查。
参数timeBetweenEvictionRunsMillis,设定间隔每过多少毫秒进行一次后台对象清理的行动。如果这个值不是正数,则实际上不会进行后台对象清理。
参数numTestsPerEvictionRun,设定在进行后台对象清理时,每次检查几个对象。如果这个值不是正数,则每次检查的对象数是检查时池内对象的总数乘以这个值的负倒数再向上取整的结果??也就是说,如果这个值是-2(-3、-4、-5……)的话,那么每次大约检查当时池内对象总数的1/2(1/3、1/4、1/5……)左右。
参数minEvictableIdleTimeMillis,设定在进行后台对象清理时,视休眠时间超过了多少毫秒的对象为过期。过期的对象将被回收。如果这个值不是正数,那么对休眠时间没有特别的约束。
参数testWhileIdle,则设定在进行后台对象清理时,是否还对没有过期的池内对象进行有效性检查。不能通过有效性检查的对象也将被回收。
另一个比较特别的构造方法是GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例;
参数config则指明一个包括了各个参数的预设值的对象(详见《GenericObjectPool.Config》一节)。
剩下的五个构造函数则是最复杂的构造方法在某方面的简化版本,可以根据情况选用。它们是:

GenericObjectPool(PoolableObjectFactory factory, int maxActive)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, boolean testOnBorrow, boolean testOnReturn)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn)
这种对象池不可以在没有Jakarta Commmons Collections组件支持的情况下运行。

GenericObjectPool.Config
调用一个有很多的参数的方法的时候,很可能将参数的位置和个数搞错,导致编译或运行时的错误;阅读包含了有很多参数的方法调用的代码的时候,也很可能因为没有搞对参数的位置和个数,产生错误的理解。因此,人们往往避免给一个方法安排太多的参数的做法(所谓的“Long Parameter List”)。不过,有些方法又确实需要许多参数才能完成工作。于是,就有人想到了一种将大批的参数封装到一个对象(称为参数对象,Parameter Object)里,然后将这个对象作为单一的参数传递的两全其美的对策。

因为生成GenericKeyedObjectPool时可供设置的特性非常之多,所以它的构造方法里也就难免会需要不少的参数。GenericKeyedObjectPool除了提供了几个超长的构造方法之外,同时也定义了一个使用参数对象的构造方法。所用参数对象的类型是GenericKeyedObjectPool.Config。

GenericKeyedObjectPool.Config定义了许多的public字段,每个对应一种可以为GenericKeyedObjectPool设置的特性,包括:

int maxActive
int maxIdle
long maxWait
long minEvictableIdleTimeMillis
int numTestsPerEvictionRun
boolean testOnBorrow
boolean testOnReturn
boolean testWhileIdle
long timeBetweenEvictionRunsMillis
byte whenExhaustedAction
这些字段的作用,与在GenericKeyedObjectPool最复杂的构造方法中与它们同名的参数完全相同。

使用的时候,先生成一个GenericKeyedObjectPool.Config对象,然后将个字段设置为想要的值,最后用这个对象作为唯一的参数调用GenericKeyedObjectPool的构造方法即可。

注意:使用有许多public字段、却没有任何方法的类,也是一个人们往往加以避免的行为(所谓的“Data Class”)。不过这次GenericKeyedObjectPool特立独行了一回。

带键值的对象池
有时候,单用对池内所有对象一视同仁的对象池,并不能解决的问题。例如,对于一组某些参数设置不同的同类对象??比如一堆指向不同地址的java.net.URL对象或者一批代表不同语句的java.sql.PreparedStatement对象,用这样的方法池化,就有可能取出不合用的对象的麻烦。

可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是,如果使用普通的ObjectPool来实施这个计策的话,因为普通的PoolableObjectFactory只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的PoolableObjectFactory,工作量相当可观。这种时候就适合调遣Pool组件中提供的一种“带键值的对象池”来展开工作了。

Pool组件采用实现了KeyedObjectPool接口的类,来充当带键值的对象池。相应的,这种对象池需要配合实现了KeyedPoolableObjectFactory接口的类和实现了KeyedObjectPoolFactory接口的类来使用(这三个接口都在org.apache.commons.pool包中定义):

KeyedPoolableObjectFactory和PoolableObjectFactory形式如出一辙,只是每个方法都增加了一个Object key参数而已:

makeObject的参数变为(Object key)
activateObject的参数变为(Object key, Object obj)
passivateObject的参数变为(Object key, Object obj)
validateObject的参数变为Object key, Object obj)
destroyObject的参数变为(Object key, Object obj)
另外Pool组件也提供了BaseKeyedPoolableObjectFactory,用于充当和BasePoolableObjectFactory差不多的角色。

KeyedObjectPool和ObjectPool的形式大同小异,只是某些方法的参数类型发生了变化,某些方法分成了两种略有不同的版本:

用Object borrowObject(Object key)和void returnObject(Object key, Object obj)来负责对象出借和归还的动作。
用void close()来关闭不再需要的对象池。
用void clear(Object key)和void clear()来清空池中的对象,前者针对与特定键值相关联的实例,后者针对整个对象池。
用int getNumActive(Object key)和int getNumActive()来查询已借出的对象数,前者针对与特定键值相关联的实例,后者针对整个对象池。
用int getNumIdle(Object key)和int getNumIdle()来查询正在休眠的对象数,前者针对与特定键值相关联的实例,后者针对整个对象池。
用void setFactory(KeyedPoolableObjectFactory factory)来设置要用的KeyedPoolableObjectFactory实例。
void clear、int getNumActive、int getNumIdle和void setFactory的各种版本都仍然是可以由具体实现自行决定是否要支持的方法。如果所用的KeyedObjectPool实现不支持这些操作,那么调用这些方法的时候,会抛出一个UnsupportedOperationException异常。

KeyedObjectPoolFactory和ObjectPoolFactory的形式完全相同,只是所代表的对象不同而已。

 

 

一个博客:

使用Jakarta Commons Pool处理对象池化

http://www.ibm.com/developerworks/cn/java/l-common-pool/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值