WebLogic/Oracle環境でoracle.sql.CLOBを使うとClassCastExceptionが発生

DB
Oracle 9i (9.2.0)
AP
WebLogic Server 8.1

の環境でCLOBをRead/Writeする。
だが、java.sql.Clobインターフェイスが提供する、CLOBに書き込むためのjava.io.Writerを取得するsetCharacterStream()メソッドは、Oracle JDBCドライバ標準oracle.sql.CLOBでは未実装*1で、代わりにgetCharacterOutputStream()を使うことになるのだが、この時点でOracle JDBCライブラリへの依存を余儀なくされる。

さらにWLS(WebLogic Server)はJDBCドライバを自前で実装し、トランザクションやコネクションプールを制御している。メリットは随分あるものの、互換性の問題を起こしがちだ。
たとえば、同じ実装クラスを参照しているはずなのに、WLS内部のラップでResultSet#getClobの返却値がoracle.sql.CLOBと互換性を失いClassCastExceptionが発生する。

WLSにはOracleThinClobというClobの拡張インターフェイスがあるので、これでキャストして立派にgetOutputCharacterStream()が使えるようになる。しかし、WLSのOracle依存実装に依存する*2と、整備された単体テストクラスが完全に役立たずだ。

そこで、単体動作もWLS上での動作も保証するため、

This is a convinience method to override by subclasses in case if application server wraps the sql connection and access to vendor specific BLOB is not possible via direct cast (i.e. in case of Weblogic server the oracle.sql.BLOB is actually weblogic.jdbc.vendor.oracle.OracleThinBlob)

要は呼び出される可能性のあるすべてのドライバに対応するコードを書くことである。

    protected Blob writeToBlob(ResultSet rs, byte[] data) throws SQLException { 
        // Object to be returned back 
        Blob blob = null; 
        // find out which "Blob" implementation is in result set 
        Object blobObject = rs.getBlob(1); 
        // if it's "Oracle" blob cast it accordingly 
        if (blobObject instanceof oracle.sql.BLOB) { 
            oracle.sql.BLOB dbBlob = (oracle.sql.BLOB) rs.getBlob(1); 
            dbBlob.putBytes(1, data); 
            blob = dbBlob; 
        } else if (blobObject instanceof weblogic.jdbc.vendor.oracle.OracleThinBlob) { 
            // this is Weblogic specific wrapper for Oracle "thin" blob 
            weblogic.jdbc.vendor.oracle.OracleThinBlob dbBlob = (weblogic.jdbc.vendor.oracle.OracleThinBlob) rs.getBlob(1); 
            dbBlob.putBytes(1, data); 
            blob = (Blob) dbBlob; 
        } else { 
            throw new RuntimeException("Unknown Blob implementation [" + (blobObject != null ? blobObject.getClass().getName() : null) + "]"); 
        } 
        return blob; 
    }

やばい。マジやばい。マジ真理に気が付いた。"Write once, Run Anywhere"を実現するとは、つまり「動く可能性のあるすべてのプラットフォーム分コードを書け」*3
http://jira.opensymphony.com/browse/QUARTZ-53;jsessionid=BOAIFGACPOKK?page=history

脈ありなのはこれか。
http://forum.java.sun.com/thread.jspa?threadID=540585&messageID=2622371
WebLogicoracle.sql.BLOBが使えない人の相談。

I use Oracle 9i and weblogic 8.1 on win2k platform.

But I met java.lang.ClassCastException for this statement
myBlob = (BLOB)results.getBlob(1);

それに対する回答が、

myBlob = (BLOB)results.getObject(1);

As per the JDBC spec the ResultSet 's getBlob method is supposed to return java.sql.Blob, this could result in a ClassCastException if u r trying to cast to a oracle.sql.Blob.

ResultSet#getObject(int)で受ける。これでうまく行ったみたい。

Per shr_18's suggestion, I changed my (BLOB)results.getBlob(1) to (BLOB)results.getObject(1) and the code began working again. Making note of this here incase others find new ClassCastExceptions raised after changing jdks.

ラッパーをダマせ! YO!*4

http://www.hibernate.org/56.247.htmlという手もあるな。

         
            Writer tempClobWriter = null;
            CLOB tempClob = CLOB.createTemporary(conn, true,
CLOB.DURATION_SESSION);
            try {
            tempClob.open(CLOB.MODE_READWRITE);
            tempClobWriter = tempClob.getCharacterOutputStream();
            tempClobWriter.write((String) value);
            tempClobWriter.flush();
            } finally {
            if (tempClobWriter != null)
            tempClobWriter.close();
            tempClob.close();
            }
            
            st.setClob(index, (Clob) tempClob);

*1:確かにset〜でjava.io.Writerを返すというのは、JDBC標準ともあろうものが、非常におかしい話だ。

*2:二重にタチが悪い。親のスネをかじるニート女に寄生するヒモ男のようだ。

*3:それって新しくなくない? わざわざスローガンにすることなくない? みんなダマされてなくない?

*4:○:wrapper ×:rapper