- PR -

配列のIN・OUTパラメータを持つストアドプロシージャの実行について

1
投稿者投稿内容
がちゃぴん
会議室デビュー日: 2002/02/06
投稿数: 19
お住まい・勤務地: 東京都・いろいろ
投稿日時: 2004-07-23 18:46
掲題の件について、J2EEアプリケーションからオラクルのストアドプロシージャを実行したいのですが、以下のような例外が発生しています。何かご存知の方がいらっしゃいましたら、
ご回答の程、宜しくお願いします。なお、オラクルとIBMのサポートサイトにも送信し、
ただいま回答待ち中です。


【環境】
OS :Windows2000
DB racle8i Release 8.1.7.0.0
Driver:Oracle JDBC driver 9.2.0.1.0(OJDBC14.jar)
TOOL:IBM Websphere Stadio Application Developer 5.1 (テスト環境WAS5.1)
JDKレベル:1.3


【概要】
配列のINOUTパラメータを持つストアドプロシージャ実行したいが、CallableStatementの取得時に、「com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatement」内で
java.lang.ClassCastExceptionが発生して実行できない。


【目的】
・オラクルのストアドプロシージャをJ2EEアプリケーションから実行したい。
・実行したいプロシージャ(ファンクション)の戻り値、又はIN、OUTパラメータでは、
PL/SQL固有の「OWA_UTIL.IDENT_ARR」型がある。
 ※「OWA_UTIL.IDENT_ARR」は、VARCHAR(30)を1つの要素とした配列型です。
  

【現在わかっていること】
・配列パラメータを持たない通常のプロシージャやファンクションについては、
 実行できることを確認している。
・配列パラメータを持つプロシージャの実行方法の資料として、以下のオラクルの
 開発ガイドを参照し試作している。
 「Oracle8i JDBC 開発者ガイドおよびリファレンス リリース8.1」
  http://otn.oracle.co.jp/document/products/oracle8i/817/general2.html
 「JDBC からのPL/SQL 索引付き表へのアクセス: IndexTableExample.java」
・開発ガイドから、配列パラメータを扱うには「java.sql.CallableStatement」ではなく、
 オラクルのJDBCドライバの「oracle.jdbc.driver.OracleCallableStatement」を
 使用する。
・サンプルコードのキャスト部分の内容
 OracleCallableStatement procin = (OracleCallableStatement)conn.prepareCall("begin procin (?); end;");


【障害内容】
・配列のINOUTパラメータを持つストアドプロシージャ実行したいが、
 CallableStatementの取得時に、「com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatement」内で
java.lang.ClassCastExceptionが発生して実行できない。

・オラクルの開発ガイドにあるサンプルコードと同様にコネクションからCallableStatement取得時に
 OracleCallableStatementにキャストしているが、そこでClassCastExceptionが発生する。
 以下、コード。

 (OracleCallableStatement)getConnection().prepareCall(
"{ ? = call java_test.sample() }" );

 ※getConnectionメソッドは、内部のメソッドです。
ちなみにこの例ではファンクションの戻り値が配列ですが、この時点で例外が
  発生するので、そのご戻り値が取得できるかどうかまで達していません。

・ClassCastException発生時のスタックトレースです。

2004-07-21 10:41:35,188 ERROR - com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatement
java.lang.ClassCastException: com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatement
at jp.co.xxx.xxxxx.aaaaa.xxxxx(aaaaa.java:40)←この行が先程のキャストしている個所
            ・
            ・(中略)
            ・
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at com.ibm.ws.webcontainer.servlet.StrictServletInstance.doService(StrictServletInstance.java:110)
at com.ibm.ws.webcontainer.servlet.StrictLifecycleServlet._service(StrictLifecycleServlet.java:174)
at com.ibm.ws.webcontainer.servlet.IdleServletState.service(StrictLifecycleServlet.java:313)
at com.ibm.ws.webcontainer.servlet.StrictLifecycleServlet.service(StrictLifecycleServlet.java:116)
at com.ibm.ws.webcontainer.servlet.ServletInstance.service(ServletInstance.java:283)
at com.ibm.ws.webcontainer.servlet.ValidServletReferenceState.dispatch(ValidServletReferenceState.java:42)
at com.ibm.ws.webcontainer.servlet.ServletInstanceReference.dispatch(ServletInstanceReference.java:40)
at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.handleWebAppDispatch(WebAppRequestDispatcher.java:974)
at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.dispatch(WebAppRequestDispatcher.java:555)
at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.forward(WebAppRequestDispatcher.java:200)
at com.ibm.ws.webcontainer.srt.WebAppInvoker.doForward(WebAppInvoker.java:119)
at com.ibm.ws.webcontainer.srt.WebAppInvoker.handleInvocationHook(WebAppInvoker.java:276)
at com.ibm.ws.webcontainer.cache.invocation.CachedInvocation.handleInvocation(CachedInvocation.java:71)
at com.ibm.ws.webcontainer.cache.invocation.CacheableInvocationContext.invoke(CacheableInvocationContext.java:114)
at com.ibm.ws.webcontainer.srp.ServletRequestProcessor.dispatchByURI(ServletRequestProcessor.java:186)
at com.ibm.ws.webcontainer.oselistener.OSEListenerDispatcher.service(OSEListener.java:334)
at com.ibm.ws.webcontainer.http.HttpConnection.handleRequest(HttpConnection.java:56)
at com.ibm.ws.http.HttpConnection.readAndHandleRequest(HttpConnection.java:618)
at com.ibm.ws.http.HttpConnection.run(HttpConnection.java:439)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:593)


【最後に】
 ・ibmパッケージ配下のクラスで発生しているので、WAS上では使用できない等の
  制約があるのでしょうか?WASのIBMパッケージ配下のクラスを介して、
  JDBCドライバが呼ばれますが、ドライバとWASの処理でよく挙動が違うことが
  ありますが、これもそうなんでしょうか。

 ・配列パラメータを使用する場合に他の方法はあるのでしょうか?
  無理くりにいろいろ試しましたが、パラメータバインド時にSQLExceptionが発生し、
  型が違うとか無効だとか言われたりして、今のところ他の方法では出来ていないです。

以上、長くなりましたが、何かご存知の方がいらっしゃいましたら、宜しくお願いします。
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2004-08-05 15:11
<恥ずかしいので消去>


#またよく読まずに書き込んでしまいました。ごめんなさい。

WebSphereのコネクションプーリングの機構が、Oracle JDBCドライバが提供する
クラスをラップしているようです。

WSJdbcUtil#getNativeConnection(WSJdbcConnection)というメソッドを利用して
ラッパーが委譲するOracleのコネクションを引っ張り出せる様なので、それを利用
して取り出したOracleConnectionに対してprepareCall()を呼び出せば、Oracle
CallableStatementが得られると思います。


[ メッセージ編集済み 編集者: シュン 編集日時 2004-08-05 18:12 ]
がちゃぴん
会議室デビュー日: 2002/02/06
投稿数: 19
お住まい・勤務地: 東京都・いろいろ
投稿日時: 2004-08-05 18:34
引用:

シュンさんの書き込み (2004-08-05 15:11) より:

WebSphereのコネクションプーリングの機構が、Oracle JDBCドライバが提供する
クラスをラップしているようです。
WSJdbcUtil#getNativeConnection(WSJdbcConnection)というメソッドを利用して
ラッパーが委譲するOracleのコネクションを引っ張り出せる様なので、それを利用
して取り出したOracleConnectionに対してprepareCall()を呼び出せば、Oracle
CallableStatementが得られると思います。


[ メッセージ編集済み 編集者: シュン 編集日時 2004-08-05 18:12 ]



ご回答、ありがとうございます。
WASでは、確かにいろいろラップしているんですよねー。
ドライバの機能と挙動が違ったりとかすることもあるので、たまに悩まされます。
上記の件、明日(8/6)に試してみたいと思います。
本日は取り急ぎ御礼まで。

なお、IBMサポートと日々やり取りを行っていまして、WSADの開発(アメリカ?)の方にも
サポートは問い合わせているようです。海外サイトのサンプルコードの紹介等のサポートを
受けたりいろいろ試しているのですが、その先で新たな障害にぶつかったりして
(別のClassCastExceptionが発生等)、結局、いまだに問題が解決していません。
その中でもいくつかわかったことがあるので、のちに自己レスしたいと思います。
がちゃぴん
会議室デビュー日: 2002/02/06
投稿数: 19
お住まい・勤務地: 東京都・いろいろ
投稿日時: 2004-08-06 16:18
引用:

シュンさんの書き込み (2004-08-05 15:11) より:

WebSphereのコネクションプーリングの機構が、Oracle JDBCドライバが提供する
クラスをラップしているようです。
WSJdbcUtil#getNativeConnection(WSJdbcConnection)というメソッドを利用して
ラッパーが委譲するOracleのコネクションを引っ張り出せる様なので、それを利用
して取り出したOracleConnectionに対してprepareCall()を呼び出せば、Oracle
CallableStatementが得られると思います。

[ メッセージ編集済み 編集者: シュン 編集日時 2004-08-05 18:12 ]



自己レスです。
上記の方法により、OracleCallableStatementの取得時のClassCastExceptionの発生は
解決でき、戻り値型をバインド(registerOutParameterメソッド)を行い、
executeUpdate()のコードまでは実行できました!ありがとうございます!

しかし、残念なことにexecuteUpdate()において、SQLExceptionがthrowされ、
以下のエラーが発生しました。
-------------------------------------------------------------
java.sql.SQLException: ORA-06550: 行 1、列 13:
PLS-00382: 式の型が正しくありません。
ORA-06550: 行 1、列 7:
PL/SQL: Statement ignored

at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:168)
at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:208)
at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:543)
at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1405)
at oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(TTC7Protocol.java:822)
at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.java:1446)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java:1371)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1900)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:363)
at jp.co.xxx.xxxxx.aaaaa.bbbbb(aaaaa.java:85) ←executeUpdate()してる行
-------------------------------------------------------------
ちなみに以下が戻り値型をバインドしているコードの抜粋です。
import oracle.jdbc.driver.OracleTypes;
getOraStatement().registerOutParameter( 1, OracleTypes.CURSOR );
~~~~~~~~~~~~~~~
   ↑
  OracleCallableStatementを取ってくるメソッド
-------------------------------------------------------------

上記のPL/SQLのエラー(PLS-00382)は、単純な文法ミスでも発生しますが、
既存のJavaではない別環境のシステムでも稼動している、エラーのない
ファンクションをJ2EEアプリケーションから実行した時のものです。
また、戻り値の型が違うのかとも思い、様々な型をバインド時に試しましたが、
逆にバインド時にSQLExceptionがthrowされ「列の型が無効です。」という
エラーが発生しました。よって、現在バインドしている型(OracleTypes.CURSOR)の方が
「まだ正しい(笑)」と捉えていますが、OracleTypesクラスをよく見てみると、
OracleTypes.PLSQL_INDEX_TABLE というのがあり、もしやこれではと思い
試してみたところ、スタックとレースの内容は、前述のものと同じで、
今度は以下のようなエラーが発生しました。

-------------------------------------------------------------
java.sql.SQLException:
ORA-03115: サポートされていないネットワークのデータ型または表現があります。
at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:168)
at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:208)
at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:543)
at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1405)
at oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(TTC7Protocol.java:822)
at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.java:1446)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java:1371)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1900)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:363)
at jp.co.xxx.xxxxx.aaaaa.bbbbb(aaaaa.java:85) ←executeUpdate()してる行
-------------------------------------------------------------

サポートされてないのでは、使えないのは仕方ないのですが、Oracleの
エラーメッセージリファレンスの対処方法からは、バージョンが古いのでアップしろとのこと。
使用ドライバ(9.2.0.1.0)とDBバージョン(8.1.7.0.0)が異なっているので、
ドライバのバージョンをDBと同じ物にして今まで試したことを全てやっても、
同様の結果となりました。
(バージョン合わせたのに、サポートされてないからバージョンアップしろとは・・・)

これらの結果から、ひとまず現段階での結論としては、配列のパラメータを持つ
プロシージャは実行できない...ということにしました。

それからIBMのサポートの内容でわかったことは、以下のことです。
なお、目的は配列のパラメータを持つプロシージャやファンクションを実行したいことですが、
障害としては、その途中段階のOracleCallableStatement取得時にClassCastExceptionが
発生するということです。

・OracleCallableStatement をそのまま使用することはサポートされていない。
これは、WAS wrapper の制限であり、この場合 cast exception が発生するのは
 期待される動作であること。

ということから、今、ClassCastExceptionの発生に関しては解消していますが、
OracleCallableStatement を直接使用しているので、方法としてはWAS上では、
好ましくないこと(≒実行できないかもしれないこと)かもしれないと捉え、
先ほどのひとまずの結論に至っています...

が、もし何かおわかりの方がいましたら、ご回答の程、宜しくお願いします。
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2004-08-06 19:25
Oracle JDBCはCursorに対応していないそうですよ。

Oracle JDBCで配列というと、VARRAY型で投げてjava.sql.Array型で受けるのではないでしょうか。
がちゃぴん
会議室デビュー日: 2002/02/06
投稿数: 19
お住まい・勤務地: 東京都・いろいろ
投稿日時: 2004-08-09 16:52
引用:

シュンさんの書き込み (2004-08-06 19:25) より:
Oracle JDBCはCursorに対応していないそうですよ。

Oracle JDBCで配列というと、VARRAY型で投げてjava.sql.Array型で受けるのではないでしょうか。



ご回答ありがとうございます。
今までにいろいろ試した中で、Array型もやってみましたが、再度、Array型で試したところ、
最終的には、ファンクションから配列の戻り値を取得することがついにできました!
調べているうちに以下のサイトを見つけ、そこからヒントを得ました。
http://www.oracle.com/technology/sample_code/tech/java/codesnippet/jdbc/varray/index.html

実際に今まで足りなかった点や間違いは以下の事項でした。
(1)registerOutParameter()メソッドは、引数2のものではなく引数3つのものを使い、
 DBにCREATEした型も渡す。
 ×registerOutParameter( col++, OracleTypes.CURSOR );
 ○registerOutParameter( col++, OracleTypes.ARRAY, "EMPARRAY" );

(2) (1)で指定した"EMPARRAY"は、以下の文でDBにCREATEする。
 例)「OWA_UTIL.IDENT_ARR」ではなくなったが、以下で代用
 CREATE OR REPLACE TYPE EMPARRAY is VARRAY(20) OF VARCHAR2(30)

(3)ファンクションの中で使用する"EMPARRAY"は初期化する必要がある
(4)戻り値の取得方法は、サイトのサンプルコードと同じ方法で取得

若干、ファンクションやDBに手を入れて対応する必要がありますが、
できることは確認できました。
ご回答をくださった、シュンさん、ありがとうございました!
1

スキルアップ/キャリアアップ(JOB@IT)