http://www.blogjava.net/SpartaYew/archive/2011/05/18/350480.html
http://zzldn.iteye.com/blog/711199
一、開發及運行環境
eclipse3.4.2, weblogic8.12, oracle9.2, jdk1.4.2, struts1框架。
二、背景
今天在對項目代碼review時,發現存在一個問題,在某一個Dao的代碼裏面,多了一個得到原生Connection的getConnection()方法,方法如下:
* 得到一個數據庫的連接
*
* @return 返加Connection對象
*/
public java.sql.Connection getConnection() {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.0.72:1521:ora9", "kfzx", "kfzx");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
上述代碼比較好理解,返回一個原生的Java Connection對象,但不免生出幾點疑問:
1、整個項目採用Weblogic的連接池並通過DataSource獲取連接,爲何還要單獨獨立出一個連接代碼來呢?
2、這個獨立出的連接有什麼具體的作用嗎? 難道Weblogic的連接池的DataSource連接有一些無法完成的功能?
3、通過Weblogic的DataSource建立的連接與Java原生Connection有何區別?
下面給出Weblogic的連接池並通過DataSource獲取連接的ConnectDB類:
* 通過獲取配置文件中定義的DataSourceName獲取連接(通過Weblogic維護的連接池)。
*/
public class ConnectDB {
private static DataSource dataSource = null;
private DataSource currentDataSource = null;
public ConnectDB() {
if (dataSource == null) init();
currentDataSource = dataSource;
}
synchronized private static void init() {
try {
logger.warn("******ConnectDB()..init() NOW******");
javax.naming.Context ctx = new javax.naming.InitialContext();
String dataSourceName = AppinitWebBean.getInitKeyValue("dataSourceName");
if (dataSourceName == null) dataSourceName = "kfzx";
dataSource = (DataSource) ctx.lookup(dataSourceName);
logger.warn("******ConnectDB()..init() OK******");
}
catch (javax.naming.NamingException e) {
logger.error("--ConnectDB.getConnection()--",e);
}
}
public Connection getConnection() throws SQLException {
try {
return currentDataSource.getConnection();
}
catch (SQLException e) {
logger.error("getConnection()", e);
}
return currentDataSource.getConnection();
}
}
帶着上述問題,對代碼進行分析,得到如下結果:
1、獨立出的連接代碼是單獨爲處理CLOB類型的字段數據內容而建立的,CLOB不能使用傳統的insert into 方法插入,必須通過處理CLOB的專用方法。
2、通過ConnectDB.java中的getConnection()方法雖然也能得到Java原生的Connection,但是在存取CLOB的字段時,總是出現“ClassCastException”錯誤。
出錯代碼如下:
oracle.sql.CLOB clob = ((OracleResultSet) rss).getCLOB(1);
原因是與Weblogic中的對Clob處理的代碼有轉型上的錯誤。
3、經過分析發現,應該就是由於使用通過ConnectDB.java建立的Weblogic的DataSource連接池無法處理OracleResultSet類型的CLOB數據。
爲了驗證自己的分析,將由ConnectDB.java中的getConnection()方法換爲Dao代碼中的getConnection(),
oracle.sql.CLOB clob = ((OracleResultSet) rss).getCLOB(1);代碼是完全可以正常運行的。
所以,開發人員在Weblogic的連接池之外,又重新建立了一個Java原生的Connection,用以專門處理CLOB的字段內容,似乎是無奈之舉。
但不難發現,上述代碼有下面的幾個缺點:
1、在項目中建立兩套連接(並且兩套連接指向同一個數據庫)是非常不規範的處理方式,況且其中之一併非Weblogic管理的連接池,而是一個Java原生Connection,不能利用Weblogic的連接池的優點。
2、管理兩套連接除了帶來管理上的困難,而且帶來編碼上的麻煩。
3、使用Java原生Connection是硬編碼方式,必須在不同的數據庫連接環境(數據庫連接的字串、SID、IP等)中硬編碼切換,應用網和本地來回切換,若不注意,則會造成錯誤。
通過以上的缺點,筆者認爲應該充分利用Weblogic的DataSource數據源及連接池,在整個項目中僅保留一套Connection,因此針對該問題進行研究,
使Weblogic的DataSource數據源及連接池也能夠連接正確處理CLOB。
三、分析研究
其實在ConnectDB.java中通過Weblogic的DataSource生成的Connection和Java原生Connection本質上是相同的,處理CLOB出錯的原因並非在Connection,
而在於通過數據庫連接生成的結果集(ResultSet),Java原生ResultSet和Weblogic對於ResultSet處理上是不同的,這裏就是產生CastException的原因,
這也是對oracle.sql.CLOB clob = ((OracleResultSet) rss).getCLOB(1);這行代碼進行研究後得出的結果。
weblogic服務器中,在通過datasourse獲取connection,CLOB字段取出來的就不是oracle.sql.CLOB類型,而是weblogic封裝過的OracleThinClob類型,
執行CLOB oCLOB = (CLOB) rs.getClob(1);所以cast的時候肯定會出錯,出現ClassCaseException異常。
換言之,通過Weblogic的DataSource連接獲得的CLOB的ResultSet是不能轉型爲java原生CLOB的ResultSet的。 也就是說,除了Java原生ResultSet之外,
每個應用服務器(Tomcat,weblogic,jboss等)都應該有自己處理CLOB型ResultSet的方式方法,並且不能通用(至少不可以轉型)。
目前,筆者見到的有三種處理CLOB型ResultSet的方法:
1、Java原生ResultSet,通常的代碼爲: oracle.sql.CLOB clob = ((OracleResultSet) rss).getCLOB(1);
2、Tomcat處理CLOB型ResultSet:須將lib下的classes12.jar(oracle包)刪除,但程序不需要改動。
3、通過Weblogic的DataSource獲得的連接池,可採用如下代碼:
weblogic.jdbc.vendor.oracle.OracleThinClob clob = (weblogic.jdbc.vendor.oracle.OracleThinClob)rss.getClob(1);
當然,要獲得weblogic.jdbc.vendor.oracle.OracleThinClob接口還必須將D:\bea812\weblogic81\server\lib下的weblogic.jar
拷貝到項目的E:\eclipse3.4.2\workspace\sykf\webapp\WEB-INF\lib下,才能正常使用。
鑑於本項目使用Weblogic做應用服務器,並且需要充分利用Weblogic的連接池建立的數據庫連接,因此,筆者採用第三種方式來解決這個問題,也就是說將
D:\bea812\weblogic81\server\lib下的weblogic.jar拷貝到項目的E:\eclipse3.4.2\workspace\sykf\webapp\WEB-INF\lib下。
四、先爲weblogic.jar減減肥吧
要知道,從D:\bea812\weblogic81\server\lib下的weblogic.jar獲得的該jar容量是非常大的,達36M之多,這麼大的jar包放到項目lib下有些笨拙,這個不要緊,可以對之進行減肥,將裏面不需要的包刪除掉就可以了,具體步驟如下:
1)、解壓該jar包,成爲一個文件夾。
2)、將裏面除jdbc之外的包全部刪除。
3)、重新壓縮成jar包。
筆者通過上述處理之後,成功將36M體積的Weblogic.jar減肥爲713K。
在這裏可能有讀者會有如下疑問:
你的lib下有weblogic.jar包,並且Weblogic下也有weblogic.jar包,並且兩個jar中包含的jdbc.vendor.oracle.OracleThinclob相同,兩者不會衝突嗎?
這個不必擔心,因爲包名類名雖然都相同,即使jvm也沒法區分,那JVM在處理時就只有第一個包被引入(在classpath路徑下排在前面的包),
第二個包會在classloader加載類時判斷重複而忽略。
五、下面給出Weblogic的Datasource連接的Resultset寫CLOB字段的代碼
寫方法:
Connection conn = null;
ConnectDB connectDB = new ConnectDB();
try {
conn = ( Connection ) connectDB.getConnection();
} catch (SQLException e1) {
e1.printStackTrace();
return false;
}
ResultSet rss = null;
PreparedStatement stmt = null;
//得到維修費的"情況說明"
String qksm = obj[1].toString().trim();
//得到維修費業務的業務id
String wxfId = obj[0].toString().trim();
// 向cbgl_wxf_qksm表中添加大數據
if ( null != qksm && !"".equals(qksm)) {
String sql1 = " insert into cbgl_wxf_qksm (id,qksm) "
+ " values ('" + wxfId + "',empty_clob()) ";
// 重新取出CLOB數據,賦值
String sql2 = "select qksm from cbgl_wxf_qksm where id='"
+ wxfId + "' for update";
try {
conn.setAutoCommit(false);
stmt = conn.prepareStatement(sql1);
stmt.executeUpdate(sql1);
rss = stmt.executeQuery(sql2);
if (rss.next()) {
// 得到流
weblogic.jdbc.vendor.oracle.OracleThinClob clob =
(weblogic.jdbc.vendor.oracle.OracleThinClob)rss.getClob(1);
clob.putString(1, qksm );
String sqlUpd = "update cbgl_wxf_qksm set qksm = ? " +
"where id ='" + wxfId + "'";
stmt = conn.prepareStatement( sqlUpd );
stmt.setClob( 1, (Clob) clob );
stmt.executeUpdate();
// 正式提交
conn.commit();
conn.setAutoCommit(true);
}
} catch (Exception e) {
logger.error(this, e);
//出錯後回滾事務
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return false;
} finally {
try {
if (rss != null)
rss.close();
} catch (SQLException e) {
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null && !conn.isClosed())
conn.close();
} catch (SQLException e) {
}
}
} else {
// 寫入一條空記錄到表cbgl_wxf_qksm中
String sql1 = " insert into cbgl_wxf_qksm (id,qksm) "
+ "values ('" + wxfId + "',empty_clob()) ";
try {
conn.setAutoCommit(false);
stmt = conn.prepareStatement(sql1);
stmt.executeUpdate(sql1);
} catch (Exception e) {
logger.error(this, e);
return false;
}finally{
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null && !conn.isClosed())
conn.close();
} catch (SQLException e) {
}
}
}
return true;
}
讀方法:
public WxfModel getWxfModel(long wxfid) {
WxfModel wxfModel = new WxfModel();
String qksm = "";
ResultSet rss = null;
Statement stmt = null;
//獲得數據庫連接,以Weblogic連接池代替硬編碼獲得Connection方式 sparta 10/9/25
try {
conn = connectDB.getConnection();
} catch (SQLException e1) {
e1.printStackTrace();
}
String sql1 = "select * from cbgl_wxf where wxfid='" + wxfid + "'";
ConnectDB connectDB = new ConnectDB();
CmResultSet rs = connectDB.getRs(sql1, null);
try {
if (rs.next()) {
wxfModel = WxfDao.rsToModel(rs);
}
} catch (Exception e) {
e.printStackTrace();
}
String sql2 = "select qksm from cbgl_wxf_qksm where id='" + wxfid + "'";
try {
stmt = conn.prepareStatement(sql2);
rss = stmt.executeQuery(sql2);
if (rss.next()) {
Clob clob = rss.getClob(1);
Reader in = clob.getCharacterStream();
BufferedReader br = new BufferedReader(in);
String qksmBlock = "";
try {
while (( qksmBlock = br.readLine()) != null) {
qksm += qksmBlock;
}
} catch (IOException e) {
e.printStackTrace();
}
wxfModel.setQksm(qksm);
}
} catch (SQLException e) {
logger.error(this, e);
}finally{
//關閉ResultSet, Statement, Connection , sparta 10/9/25
try{
if( rss != null) rss.close();
}catch( Exception ex ){}
try{
if( stmt != null) stmt.close();
}catch( Exception ex ){}
try{
if( conn != null && !conn.isClosed() ) conn.close();
}catch( Exception ex ){}
}
return wxfModel;
}
六、另外再給出Tomcat的連接池處理ResultSet的CLOB字段的方法
代碼與Java原生ResultSet寫CLOB型字段的代碼一樣,但是需要注意,要將項目的lib文件夾下的classes12.jar刪除纔不會出現ClassCastException錯誤。
開發項目一般都喜歡用Tomcat做測試環境,開發好後需要移到weblogic下,這時候發現問題不斷。
由於weblogic的嚴密性,程序中有些數據的轉換出現錯誤,一些小問題倒是好解決,知道在處理BLOB字段類型數據時走了不少彎路,下面就說說這個BLOB字段在這兩種web服務器類型下的不同處理情況。
我的大內容文字都是存在CLOB字段裏,BLOB用於存儲圖片。
一、Tomcat環境下的BLOB處理
在Tomcat下的BLOB處理相對簡單很多,在Tomcat下從表中搜到的BLOB字段數據都只需簡單強制轉化就可以了,看源碼:
(1)獲取BLOB數據
public BLOB getTMIMage(String TM_code,Connection con) {
BLOB blob=null;
try{
ArrayList paramList=new ArrayList();
String sql="select FIMAGE from BM_TTMIMAGE where FTMID=?";
paramList.add(TM_code);
ResultSet rst= doSelect(sql, paramList, con); //doSelect(sql, paramList, con); 封裝處理數據的過程
if(rst.next()){
blob=(BLOB)rst.getObject(1);
}
rst.close();
}catch(Exception e){e.printStackTrace();}
return blob;
}
blob=(BLOB)rst.getObject(1); 這樣的獲取方法應該是最好的,其實在默認的時候應該是blob=(BLOB)rst.getBlob(1) 的, 但有些環境下rst.getBlob(1)會報錯,我個人還是喜歡blob=(BLOB)rst.getObject(1); 這種方式。
(2)插入BLOB數據
public void SaveIMage(String Image_ID,BLOB blb,Connection con) {
try{
ArrayList paramList=new ArrayList();
StringBuffer sql=new StringBuffer();
paramList.add(Image_ID);
sql.append("select IMAGE from BM_IMAGE where IMG_ID=? for update");
ResultSet rst= doSelect(sql.toString(), paramList, con);
if(rst.next()) {
BLOB blob=(BLOB)rst.getObject(1);
OutputStream os = blob.getBinaryOutputStream();
BufferedOutputStream output = new BufferedOutputStream(os);
if(blb==null){
}else{
BufferedInputStream input = new BufferedInputStream(blb.getBinaryStream());
byte[] buff = new byte[2048]; //用做文件寫入的緩衝
int bytesRead;
while(-1 != (bytesRead = input.read(buff, 0, buff.length))) {
output.write(buff, 0, bytesRead);
}
input.close();
output.flush();
output.close();
os.close();
}
}
rst.close();
}catch(Exception e){e.printStackTrace();}
}
插入圖片數據是先存儲數據再讀取寫入圖片數據,先給BLOB一個初始化值(可以在建表時給默認值函數empty_blob())。
二、weblogic環境下的BLOB處理
(1)獲取BLOB數據
public Blob getTMIMage(String TM_code,Connection con){
Blob blob=null;
try{
ArrayList paramList=new ArrayList();
String sql="select FIMAGE from BM_TTMIMAGE where FTMID=?";
paramList.add(TM_code);
ResultSet rst= doSelect(sql, paramList, con);
if(rst.next()){
blob=rst.getBlob(1);
}
rst.close();
}catch(Exception e){e.printStackTrace();}
return blob;
}
這裏就和Tomcat環境下不一樣了,這時候結果集裏的BLOB數據取出來就不能強制轉化爲BLOB了,網上好像說這個時候的BLOB讀取的數據實際是weblogic.jdbc.vendor.oracle.OracleThinBlob了。後來,我看在weblogic下不能有對BLOB的直接處理,最好在程序應用時用java.sql.Blob類型。這裏blob=rst.getBlob(1);也不需要用blob=(Blob)rst.getObject(1);
(2)插入BLOB數據
public void SaveIMage(String Image_ID,Blob blb,Connection con){
try{
ArrayList paramList=new ArrayList();
StringBuffer sql=new StringBuffer();
paramList.add(Image_ID);
sql.append("select IMAGE from BM_IMAGE where IMG_ID=? for update");
ResultSet rst= doSelect(sql.toString(), paramList, con);
if(rst.next()){
OracleThinBlob blob=(OracleThinBlob)rst.getBlob(1);
OutputStream os = blob.getBinaryOutputStream();
BufferedOutputStream output = new BufferedOutputStream(os);
if(blb!=null){
BufferedInputStream input = new BufferedInputStream(blb.getBinaryStream());
byte[] buff = new byte[2048]; //用做文件寫入的緩衝
int bytesRead;
while(-1 != (bytesRead = input.read(buff, 0, buff.length))){
output.write(buff, 0, bytesRead);
}
input.close();
output.flush();
output.close();
os.close();
}
}
rst.close();
}catch(Exception e){e.printStackTrace();}
}
在這裏,又看到了OracleThinBlob對象,經過多次轉換,終於發現問題,對於Blob、BLOB、OracleThinBlob三種對象,有了一些理解。
Blob是java.sql.*包裏的對象
BLOB是oracle.sql.*包裏的對象
OracleThinBlob是weblogic.jdbc.vendor.oracle.*包裏的對象
問題在於這三種類型對流在轉化,我主要用的是這三種對象轉化爲輸出流和輸入流,
Blob b=new Blob();
b.getBinaryStream(); // 輸出流
BLOB bl=new BLOB();
bl.getBinaryStream(); //輸出流
bl.getBinaryOutputStream(); //輸入流
OracleThinBlob bb=new OracleThinBlob();
bb.getBinaryOutputStream(); //輸入流
可以看到,Blob只可以直接轉化爲輸出流,OracleThinBlob 只可以轉化爲輸入流,BLOB兩種流都可以轉化,當然,用BLOB就輕鬆多了,可是在weblogic下不能直接轉化BLOB,於是我就只能用Blob對象獲取數據然後轉化爲輸出流,在插入數據的時候把空的BLOB字段數據轉化爲OracleThinBlob然後獲取輸入流寫入圖片文件內容。