数据库连接不关闭造成的问题以及RowSet的使用

  这几天给项目做性能压力测试,发现一个方法压力200之后就会把整个系统弄停掉。仔细检查发现是开发人员调用数据库的写法有问题。用的是spring的jdbcTemplate,在使用回调的时候,在回调里又做了数据库的查询。只要把这个查询写在回调外执行就没有性能问题,写在里面压力大的时候马上出问题。

  查看spring的源代码发现,这两种写法唯一的区别就是,写在回调里面的时候,数据库连接未关闭就开启一个新的连接进行操作,写在外面则是先关闭了的。

  造成数据库端报错ORA-12519,数据库端给不出可用的连接来。分析这种写法造成数据库连接资源浪费有两个方面,第一,一个线程打开了两个连接,第一个连接必须等第二个连接完成了才能释放,也就是直观上连接资源占用加倍了。第二在时间上,第一个连接不能立即释放,造成其他线程获取可用连接的机会更少了。

  为了保证不出这样的性能问题,(不能确保每个开发程序员都不这样写,防止万一,因为这样写造成的性能瓶颈太明显了)决定将springJdbc封装一层,因为ResultSet必须依赖与链接,如果链接先关闭,就取不到数据了,所以在使用回调的时候,先将结果集保存在内存中,然后马上关闭链接,再用保存在内存中的结果集来进行数据的提取。

  这个想法是有了,实现前提是要了解ResultSet的数据结构,才好将数据转移到保存在内存中这个结果集中,其次是要将这个内存中的结果集也是java.sql.ResultSet的实现,这样对开发人员来说才是透明的。想着工作量是挺大的,还好有现成的接口了javax.sql.rowset.CacheRowSet,还有现成实现--->com.sun.rowset.CacheRowSetImpl,直接使用就OK了。

  以为是ok了,发现用orcale的时候会报一个数字类型精度的错误,网上有很多种解决办法,有在数据库驱动层改的,有在Spring改的,有在连接池包里改delegate的,但我不想改第三方jar包,这样部署的时候就必须用我们修改过的了,我就在封装jdbcTemplate那一层用DataSouce做判断,目前spring用的数据库连接池主要有三种,DBCP、c3p0、proxool。很遗憾DataSouce接口没提供访问,所以我不得不用反射的方式获取驱动类名。DBCP的方法是getDriverClassName;c3p0的方法是getDriverClass;proxool的方法是getDriver,这样就可以获得数据库驱动的类型了,再判断是不是orcale的驱动,如果是就使用orcale自己提供的CacheRowSet实现-->oracle.jdbc.rowset.OracleCachedRowset,如果不是还是用sun提供的实现。

  完成。

*ps,当时在想的时候还有一个思路就是线程与链接进行绑定,一个线程只允许开一个链接,在获取链接的时候先看线程局部对象里绑定的链接是否关闭,如果没关闭就用这个链接,不再新开一个链接,这个思路比较大胆,对项目里数据库调用的具体情况还不清楚,所以只是假设,但这个的实现都必须是在数据库连接池管理上去实现,所以必须是数据库连接池支持才可以,就看了spring用的三种连接池,都没有这样的配置,说明这种想法业界没有用过,既然如此就打消这种想法了。

 

------------------------------------------------分割线-----------------------------------------------------

用了几天又发现问题了,就是Oracle的rowSet实现类OracleCachedRowset不能自动类型转换,原来我们用的DBCP的DelegatingResultSet可以转字符串"0"直接转化成boolean型的false,而OracleCachedRowset就不行会报错,但我们项目里已经大量用了这种自动类型转换的方式了,没有办法只能放弃使用OracleCachedRowset,还是只能想办法规避oracle的精度返回-127的问题。

网上说的办法是用javaassist修改一下OracleResultsetMetaData的getScale方法。是Oracle9的驱动。我用反编译器打开看,有些不一样,我们用的10g的驱动,代码里做了一些判断,如果精度是-127并且oracle.jdbc.J2EE13Compliant参数是ture就返回0,说明Oracle意识到了这个bug,现在我要做的就是要将oracle.jdbc.J2EE13Compliant这个参数写到OracleDriver里,我一路查源代码,oracle驱动包是公布了修改这个参数的接口的,dbcp也是有这个接口的,但spring没有,spring为了通用性放弃了特殊需求,所以如果想在spring配置里加入这个参数,那就得修改spring的源码,这种方式是我最不愿意的。所以采用另一种简单的方法,oracle驱动也做得很好,会判断初始化OracleDriver传入的参数里有没有这个参数,如果没有会去系统参数里面去取。这样就很简单了,只要在我之前判断是不是oracle数据库那里加上一句代码System.setProperty("oracle.jdbc.J2EE13Compliant","true")就可以了。希望不要再出问题了。目前只是把结果集保持在内存中了,还有调用数据库存储过程的形势没解决呢。

 

------------------------------------------------分割线-----------------------------------------------------

问题又出了,报Invalid precision Value .Cannot be less than zero的错误,查看oracle.jdbc.driver.OracleResultSetMetaData的getPrecision方法,发现会去找describeType属性,如果是112或者113就返回-1,现在我如果是blob或者clob的字段就会返回-1,RowSetMetaDataImpl不接受负数的值作为precision,就报错了。describeType怎么来的呢,是由oralce.jdbc.driver.T4CTToac里的 oacdty字段来的,这个是Oacle Data Type的意思,查看下面的表

Oracle 10.2 Data Types

Oracle 10.2 data types (oacdty) for use when you're (bravely) exploring an Oracle trace:

1     VARCHAR2 or NVARCHAR2 
2     NUMBER 
8     LONG 
9     NCHAR VARYING, VARCHAR 
12   DATE 
23   RAW 
24   LONG RAW 
25   LONG UB2 
26   LONG SB4 
58   ANYDATA 
69   ROWID 
96   CHAR or NCHAR 
100 BINARY FLOAT 
101 BINARY DOUBLE 
102 REF CURSOR 
104 UROWID 
105 MLSLABEL 
106 MLSLABEL 
111 XMLTYPE (TABLE or REF) 
112 CLOB or NCLOB 
113 BLOB 
114 BFILE 
121 TYPE (USER-DEFINED) 
122 TYPE (TABLE OF RECORD) 
123 TYPE (VARRAY) 
178 TIME 
179 TIME WITH TIME ZONE 
180 TIMESTAMP 
181 TIMESTAMP WITH TIME ZONE 
182 INTERVAL YEAR TO MONTH 
183 INTERVAL DAY TO SECOND 
208 UROWID 
231 TIMESTAMP WITH LOCAL TIME ZONE

所以我的CLOB、BLOB 字段precision会返回-1了,这个写死的了,没有办法在哪里改配置了。用OacleCachedRowSet是可以的,但基于之前的原因但又不能用。查英文资料说

DataDirect JDBC Driver 可以用,就找了一个来,加入相应的jar包,然后class.forName("com.ddtek.jdbc.oracle.OracleDriver");url="jdbc:datadirect:oracle://IP:1521;DataBaseName=orcl"。一切OK结果却报:this driver is locked for use with embedded applications。谷歌了下说需要license。我只是用jar放入我的项目中,根本不想安装datadirect的客户端,更懒得去搞认证,干脆继续用反编译找到com.ddtek.jdbc.base.BaseConnection的open方法,就是在这里给属性lockedEmbedding赋值了,只要把这个属性弄成false就可以通过了。这里调用了一系列方法赋值,我没继续看下去了,就准备在这个做一个切点,直接给false值。简单的学习了下JAVAssist的使用,来改这个字节码文件,得到我想要的false值。

public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        try {
            pool.insertClassPath("D:\\base.jar"); //将jar包放在D盘下的
            CtClass cc = pool.get("com.ddtek.jdbc.base.BaseConnection");           
            CtMethod m = cc.getDeclaredMethod("open");
            m.instrument(new ExprEditor(){ 
        @Override         
public void edit(MethodCall m)throws CannotCompileException {           if(m.getClassName().equals("com.ddtek.jdbc.base.BaseLicenseUtility")&&m.getMethodName().equals("isLocked")){             m.replace("$_=false;");           };         } }); cc.writeFile("D:\\test"); } catch (Exception e) { e.printStackTrace(); } }

然后把这个生成的class文件替换jar包里原来那个class文件就可以了。只是很粗略的学习了下JAVAssist,还有很多很强大的功能,也肯定有比我这种写法更好的,效果都差不多,只是可读性更好,运行成功,原来破解软件也没想象中那么高端嘛。

 

posted @ 2014-04-21 14:35  寂静沙滩  阅读(5185)  评论(0编辑  收藏  举报