隠された例外(その3)

2008/08/14 xx:xx:xx  oracle.jdbc.driver.OracleStatement [doExecuteWithTimeout] SQL: INSERT INTO ADDBOOK ORDER BY FULLNAME (FULLNAME, ADDRESS, EMAIL, MARRIED, GENDER, UPDDATE) VALUES (?, ?, ?, ?, ?, ?)
2008/08/14 xx:xx:xx  oracle.jdbc.driver.T4CTTIoer [processError] Throwing SQLException: ORA-00926: VALUESキーワードがありません。

コメント頂いた直後はCachedRowSetWriterのバグと決めつけていたのだが、ふと思い出してXMLシリアライズしたWebRowSetのプロパティ部を見てみた。

   
      
         SELECT * FROM ADDBOOK ORDER BY FULLNAME
         1008
         MyDataSource
         :     
         ADDBOOK ORDER BY FULLNAME
     :
     
     :
     以下略
     :
  

CachedRowSetWriterは同期時(acceptChanges)に内部でSQLコマンドを合成するが、その時のテーブル名にWebRowSet#getTableName()の戻り値を使っている。この値がそのまま使われていると考えて間違いなさそうだ。
それにしてもどうしてテーブル名に"ORDER BY"が付加されてしまっているのだろう。自前のシリアライズザクラス(RowSetXmlWriter実装)を確認してみたが、こんなテーブル名はセットしていないぞ。

アセンブルしていないのでここからは推測になるが、WebRowSetの実装(WebRowSetImpl extends CachedRowSetImpl)ではSQLコマンドが設定されると(#setCommand)、ご丁寧にもSQLからテーブル名を類推して、セットしているのではないだろうか。恐らくその類推の程度は最悪で、単に"SELECT * FROM"の後をテーブル名と決めうちしていると思われる。これは酷い。

この問題の回避方法だが、

  • setCommandで設定するSQLコマンドをCachedRowSetImplが正しく解釈できる記述だけに限定する

具体的には、SetCommandメソッドでセットする値にはテーブル名の後に何も書かないことになるが、これはちょっと現実的ではない。

  • WebRowSetImplを継承したクラスで、setTableNameメソッドをオーパライドして、正しいテーブル名だけがセットされるようにする

こんな感じで先頭のテーブル名と思われるワード以外は全て切り捨てる(トークンの区切りは空白であることを前提にしている、ベタだがご容赦)

    @Override
    public void setTableName(String tableName) throws SQLException {
        String[] tokens = tableName.split("[\\s]");
        if ( tokens != null && tokens.length > 1 ) {
            super.setTableName(tokens[0]);
        } else {
            super.setTableName(tableName);
        }
    }

うーん、格好悪いなぁ。