Weblogic、Tomcat、Oracle與Clob

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字段的代碼

        寫方法:

public boolean importQKSM( Object[] obj){
        
        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;
    }


讀方法:

// 根據wxfid得到維修費的model
    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然後獲取輸入流寫入圖片文件內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章