這幾天給項目做性能壓力測試,發現一個方法壓力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,還有很多很強大的功能,也肯定有比我這種寫法更好的,效果都差不多,只是可讀性更好,運行成功,原來破解軟件也沒想象中那么高端嘛。