このページで解決しない高度なJavaのシステム障害はJaTSにお任せください

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型にマッピングするか調べることができます。
Page Top

<Q10-2>JDBCを使用して、SQLのUpdate文やInsert文を使用する際、日付・時刻の投入方法がわかりません。


<発生環境>
OS Linux / Solaris
JDK JDK1.3
Vender Sun
<A10-2>
日付、時刻のフォーマットは各データベースによって異なるため、データベースに依存しないプログラミングを行うためには、JDBCエスケープ構文を使用するか、PreparedStatementを使用してjavaのTimestamp型をバインドしてください。どちらかといえば後者の方が確実です。
Page Top

<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
<A10-3>
上記のようなトラブルは、ぜひJavaシステム障害解決サービス(JaTS)へお問い合わせ下さい。
Page Top

<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();

Page Top

<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のドライバは上位互換ではないので、同じバージョンを使用するようにしましょう。
Page Top

<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の利用事例~メモリリーク~をご参照ください。

Page Top

<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メールマガジンにて配信しています。
 情報を取得したい方は、メールマガジンのバックナンバーを参照して下さい。



Page Top

<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()メソッドの引数に指定するスキーマ名/テーブル名等を小文字にしている場合、大文字に変更することで解決できます。
Page Top

<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 テーブル名
Page Top

<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リファレンスを参照してください。
Page Top

注意:本文書の内容に誤りがあり、またこの文書によって不利益を被っても、
Acroquest Technology 株式会社は一切関知いたしません。

  • 現象別Index
  • 原因別Index

Find Bugsバグ詳細

Find Bugs Bug Descriptions日本語版

RSSで更新情報を取得する

RSSとは、ホームページの更新情報を配信する為のフォーマットです。
RSSを利用すると、登録したページの情報が更新された場合に、更新情報を自動的に受け取る事ができます。

詳細

弊社小森が執筆致しました

Javaでオブジェクト指向開発

Javaプログラミング言語習得において、新人プログラマーの最初の障害は「オブジェクト指向の壁」です。
本書は、Javaのソフトウェア開発を中心に事業を発展させてきたAcroquest社の新人教育セミナーを加筆・書籍化したもので、大卒の新人に対して、ゼロからJava言語を教えてきた実績をフィードバックしています。

メールマガジン配信中

Javaトラブルシューティングのメルマガをはじめました!是非ご購読ください

詳細