Trouble 10: DBがうまく操作できません
<Q10-1>JDBCを使用してSQLを実行した結果のresultSetからgetObjectメソッドを使用して値を取り出す場合、実際の戻り値の型はJDBCドライバによって異なってしまいます。
実際にはOracleを使用してDB上でnumber型として定義したフィールドをgetObjectで取り出したとき、戻り値はjava.math.BigDecimal型となり、そのままではlongやintへのキャストが行えませんでした。
| <発生環境> |
| OS |
Solaris |
| JDK |
JDK1.3 |
| Vender |
Sun |
- <A10-1>
- getObjectメソッドの結果を instanceof 演算子を使用して型を判定することで回避できます。より汎用性の高い解決策としては、ResultSetMetadataを使用することで、使用しているJDBCドライバがDB上のどの型をどのJava-SQL型にマッピングするか調べることができます。
<Q10-2>JDBCを使用して、SQLのUpdate文やInsert文を使用する際、日付・時刻の投入方法がわかりません。
| <発生環境> |
| OS |
Linux / Solaris |
| JDK |
JDK1.3 |
| Vender |
Sun |
- <A10-2>
- 日付、時刻のフォーマットは各データベースによって異なるため、データベースに依存しないプログラミングを行うためには、JDBCエスケープ構文を使用するか、PreparedStatementを使用してjavaのTimestamp型をバインドしてください。どちらかといえば後者の方が確実です。
<Q10-3>短時間に連続してデータベースの更新を行うとNullPointerExceptionが発生します。
データベースにログを書き込んでいます。
短時間に大量にログを出力すると、以下のNullPointerExceptionが発生します。
java.lang.NullPointerException
at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:310)
at oracle.jdbc.driver.OracleStatement.cancel(OracleStatement.java:2963)
at oracle.jdbc.driver.OracleCancelThread.run(OracleCancelThread.java:53)
データベースの更新頻度は、11件/秒程度です。
例外は数万レコードに1回の割合で発生します。
例外発生によってアプリケーションがダウンすることはありませんが、一定時間、処理が停止してしまうため、困っています。この停止時間は、データベースへのアクセスに指定したタイムアウト時間と等しいようです。
ただし、成功時のクエリ実行時間を見ても、タイムアウト時間とはかなりの開きがあり、タイムアウトが発生するようには思えません。
ログも検索対象のため、データベースへの蓄積を止めるわけにもいきません。
アプリケーションのソースコードをチェックしましたが、NullPointerExceptionが発生するような箇所は全くありませんでした。
どんな原因が考えられるでしょうか?
| <発生環境> |
| OS |
any |
| JDK |
any |
| Vender |
any |
| DB |
Oracle 9i Release 2 |
<Q10-4>Oracle9i用のドライバを使用してバッチ処理を行うと意図した結果が得られません。
Oracle9iを使用してアプリケーション作成をしています。
20回のexecuteUpdateをまとめるバッチ処理を行っているのですが、バッチ内の特定のクエリが実行されません。
原因は不明ですが、まとめて実行する回数を20回から19回に変えると全て実行されます。
対象部分のコードは以下の様になっています。
// バッチ回数の設定
((OracleConnection)conn).setDefaultExecuteBatch(20);
// PreparedStatementを生成する。
PreparedStatement ps =
conn.prepareStatement ("insert into dept values (?, ?, ?)");
// 1番目のクエリを設定し、実行する。
ps.setInt (1, 1);
ps.setString (2, "row1");
ps.executeUpdate ();
// 2番目のクエリを設定し、実行する。
ps.setInt (1, 2);
ps.setString (2, "row2");
ps.executeUpdate ();
// ・・・以下、20番目のクエリまで実行する。
| <発生環境> |
| OS |
any |
| JDK |
any |
| Vender |
any |
| DB |
Oracle 9i Release 2 |
- <A10-4>
- OracleのJDBC Thin ドライバ実装の不具合です。
OracleConnection#setDefaultExecuteBatchは、JDBC 1.0のOracle拡張APIです。
このバージョンのJDBC Thin ドライバでは、このAPIに不具合があります。
# APIそのものがThinドライバではサポートされていない可能性もあります。
# というのも、OCIドライバに対してはAPIのサンプルが公開されているのに、
# Thinドライバに対しては公開されていないからです。
対処策としては、以下の2つが考えられます。
① JDBC2.0以降でサポートされた、標準のバッチ処理APIを使用する。
←具体的には、OracleConnection#setDefaultExecuteBatchでなく、Statement#addBatchを使用する。このAPIには不具合は存在しない。
② Oracle10g用のJDBC Thin ドライバを使用する。
←動作確認をしたところ、正常に動作したが、Oracleから正式に修正された事が公開されているわけではない。
②は動作確認したのみなので、基本的には①を選ぶべきです。
但し、①を選ぶとコードの修正が発生するため、状況によっては②を選ぶ事も検討する必要があるでしょう。
●参考
②を採用する場合、上記問題での修正後コードは以下の様になります。
// PreparedStatementの生成、実行。
// PreparedStatementを生成する。
PreparedStatement ps =
conn.prepareStatement ("insert into dept values (?, ?)");
// 1番目のクエリを設定する。
ps.setInt (1, 1);
ps.setString (2, "row1");
ps.addBatch();
// 2番目のクエリを設定する。
ps.setInt (1, 2);
ps.setString (2, "row2");
ps.addBatch();
// ・・・以下、20番目のクエリまで設定する。
// バッチを実行する。
ps.executeBatch();

<Q10-5>PostgreSQL Ver7.4.2でselect処理中のinsert処理が遅延してしまいます。
PostgreSQLを使用してWebアプリケーションを作成していますが、定期的に行うinsert処理が遅延する場合があります。
実際にDBに対してSQLを発行しようとはしているのですが、DBが受け付けていない様です。
該当するinsert処理を調査しましたが、特に問題は見付かりませんでした。
| <発生環境> |
| OS |
FedoraCore3(Linux 2.4.18) |
| JDK |
1.4.2_10 |
| Vender |
Sun |
| DB |
PostgreSQL Ver7.4.2 |
- <A10-5>
- DBとJDBCドライバのバージョンは合っていますか?
PostgreSQL Ver7.4.2に対してPostgresQL Ver8.0用のJDBCドライバを適用すると、高負荷時にこのような現象が発生します。
PostgreSQLのドライバは上位互換ではないので、同じバージョンを使用するようにしましょう。
<Q10-6>Jakarta Commons DBCPを利用しているとメモリ使用量が増加してしまいます。
DB接続の高速化のためにJakarta Commons DBCPを利用しています。
DBCPを使った高速化は達成しましたが、
長時間動作させるとメモリ使用量が徐々に増加してきます。
| <発生環境> |
| OS |
any |
| JDK |
any |
| Vender |
any |
- <A10-6>
- Jakarta Commons DBCPは高速化のために
PreparedStatementのプーリング機能を提供しています。
今回の問題は特定のSQLを発生するプログラムにおいて、
PreparedStatementのプーリングを有効にしているために発生している可能性が高いです。
Jakarta Commons DBCPはDBへの1接続ごとに
PreparedStatementオブジェクトをプーリングしています。
SQL文が異なれば新たにPreparedStatementオブジェクトが生成され、
DBへの接続が破棄されるまで蓄積され続けます。
このため次の条件を満たすプログラムではPreparedStatementオブジェクトが
大量にプーリングされることになります。
1.DBへの1つの接続が長期間クローズされない
2.PreparedStatementを利用するSQL文が何パターンも存在する
このような条件を満たすプログラムではJakarta Commons DBCPの
PreparedStatementのプーリング機能を無効にする必要があります。
具体的にはorg.apache.commons.dbcp.PoolableConnectionFactoryのコンストラクタにおいて、
org.apache.commons.pool.impl.StackKeyedObjectPoolFactoryにnullを指定します。
joclファイルを利用する場合には次のように指定します。
<object
class="org.apache.commons.pool.impl.StackKeyedObjectPoolFactory"
null="true"/>
アプリケーションサーバ上で動作しているプログラムにおいて、JNDIサービスが有効な場合には、BasicDataSourceを利用し以下の通り記述します。
<parameter>
<name>poolPreparedStatements</name >
<value>false</value>
</parameter>
なおこのメモリリークの発見と原因調査は、
ENdoSnipeを利用することで容易に行うことができます。
詳しい利用事例については、ENdoSnipeの利用事例~メモリリーク~をご参照ください。

<Q10-7>Jakarta Commons DBCPを利用するとパフォーマンスが落ちてしまいます。
DBの利用を効率化するために、プロジェクトにJakarta Commons DBCPを導入しました。
DBへの接続がプーリングされるため、高速化することを期待したのですが、
かえってパフォーマンスが落ちてしまいました。
アプリケーションのログを見ると IndexOutOfBoundsException が発生しているようです。
| <発生環境> |
| OS |
any |
| JDK |
any |
| Jakarta Commons DBCP |
1.2.2 |
| Jakarta Commons Pool |
1.3 |
- <A10-7>
- この現象は、接続確認用SQL文の設定が誤っている場合に発生します。
Jakarta Commons DBCPのコネクションプールは、
定期的に接続確認用SQL文を実行することで、
プール内のコネクションが利用できるかを確認しています。
接続確認用SQL文の設定にミスがある場合、
接続確認が行えず、コネクションプールが正常に動作しません。
そのため、本来なら高速化するはずのJakarta Commons DBCPの処理が、
かえって遅い処理になる可能性があります。
[対策]
この問題に対応するためには、Jakarta Commons DBCPで設定している接続確認用SQL文を
正しいものに変更する必要があります。
変更箇所はJakarta Commons DBCPの利用方法によって2種類あります。
1.JOCLファイルを利用して初期化をしている場合
Jakarta Commons DBCPを以下のようなソースコードで初期化している場合は、
JOCLファイルを変更する必要があります。
JOCLファイルの場所や記述内容については、
Jakarta Commons DBCPのJavadocを参照してください。
JOCLファイルを抜粋すると次の通りです。
この後ろから4番目の行が接続確認用SQL文になります。
<object class="org.apache.commons.dbcp.PoolableConnectionFactory"
xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">
<object class="org.apache.commons.dbcp.
DriverManagerConnectionFactory">
・・・(中略)・・・
</object>
<object class="org.apache.commons.pool.impl.GenericObjectPool">
・・・(中略)・・・
</object>
<object class="org.apache.commons.pool.impl.
StackKeyedObjectPoolFactory"/>
<string value="SELECT COUNT(*) FROM DUAL"/> <!-- 接続確認用SQL文 -->
<boolean value="false"/>
<boolean value="true"/>
</object>
2.JOCLファイルを利用して初期化をしている場合
接続確認用SQL文は、PoolableConnectionFactoryのコンストラクタの第4引数で指定するか、
もしくはPoolableConnectionFactory.setValidationQuery()で指定することができます。
以下に初期化の一例を示します。
ConnectionFactory factory
= new DriverManagerConnectionFactory(url, userid, password);
new PoolableConnectionFactory(factory,
new GenericObjectPool(null, 10, 1, 2000, 10, false, false, 10000,
5, 5000, true),
null, "SELECT COUNT(*) FROM DUAL", false, true);
この記事の詳細な情報をJTSメールマガジンにて配信しています。
情報を取得したい方は、メールマガジンのバックナンバーを参照して下さい。

<Q10-8>JDBCのDatabaseMetaDataを使用したとき、テーブル情報が取得できません。
| <発生環境> |
| OS |
Solaris8 |
| JDK |
JDK1.3.1_02 |
| Vender |
Sun |
| DB |
Oracle8 Enterprise Edition Release 8.0.6.0.0 |
| JDBC Driver |
Oracle JDBC Driver 8.1.6.0.1 |
- <A10-8>
- DatabaseMetaData.getColumns()メソッドの引数に指定するスキーマ名/テーブル名等を小文字にしている場合、大文字に変更することで解決できます。
<Q10-9>OracleのJDBCドライバで、ORDER BYを利用して更新可能ResultSetを取得する方法はありませんか。
| <発生環境> |
| OS |
WindowsNT 4.0 |
| JDK |
JDK1.3.1_02 |
| Vender |
Sun |
| DB |
Oracle8 Enterprise Edition Release 8.0.5.2.1 |
| JDBC Driver |
Oracle JDBC Driver 8.1.6.0.1 |
- <A10-9>
- 11-6に記述されているように、「Oracle JDBC driver 8.1.6.0.1」では、「ORDER
BY」を利用した問い合わせで更新可能ResultSetを取得することはできません。
対象となる列に索引が張ってある場合には、代替策として以下のオプティマイザ・ヒントを利用し、「ORDER BY」利用時と同様の結果を取得する事ができます。
SELECT /*+ INDEX (テーブル名 索引) */ FROM テーブル名
<Q10-10>JDBCを使用して、ある列のフィールド長を取得する方法はありませんか。
DB上に定義されているカラムのサイズを取得したい(例えば、VARCHAR(20)という定義から 20というサイズを取得したい)のですが。
| <発生環境> |
| OS |
any |
| JDK |
any |
| Vender |
any |
- <A10-10>
- JDBCでは、検索結果の表情報を持つ ResultSetMetaDataクラスがあります。このクラスを使用して取得してください。特定の列のフィールド長を取得する場合は、以下のようにします。
//(1) フィールド長を取得したい列を含むテーブルを検索する
ResultSet rset = stmt.executeQuery("SELECT ... FROM xxx");
ResultSetMetaData rmeta = rset.getMetaData();
//(2) ResultSetMetaData#getPrecision(int)でサイズを取得する
// columnは列番号
int precision = rmeta.getPrecision(column);
ResultSetMetatDataの詳細については、APIリファレンスを参照してください。
注意:本文書の内容に誤りがあり、またこの文書によって不利益を被っても、
エスエムジー株式会社は一切関知いたしません。