Berkeley DB XMLを使用して、あまり使われない文字セットでエンコーディングされたXMLドキュメントを解析できますか。
Berkeley DB XMLでは、Xerces-Cライブラリの最上層に実装されたXQueryおよびXPath 2ライブラリであるXQillaを使用して、さまざまなXMLドキュメントを解析します。 Xerces-Cは、UTF-8、UTF-16、ISO-8859-1など、多数の有名なエンコーディングを使用したXMLドキュメントを解析する機能を標準で備えています。 しかし、サポートされていないエンコーディング(たとえば、Big-5)を使用したドキュメントであっても、解決策はあります。 ICUサポートを有効にしてXerces-Cライブラリをコンパイルすれば、Berkeley DB XMLを使用して、500種類を超える文字エンコーディングのコード変換と解析ができるようになります。 Berkeley DB XML付属のbuildall.sh scriptスクリプトに次のオプションを指定して実行すれば、ICU対応のXerces-Cライブラリがビルドされます。
./buildall.sh --with-xerces-conf="-t icu"
Berkeley DB XMLでは、どの言語によるインタフェース(API)がサポートされていますか。
コンパイル済みのWindowsバイナリによってサポートされているAPIを教えてください。
Windowsバイナリでは、C++、Java、Perl、Python、PHPの各言語によるAPIがサポートされています。 Windowsバイナリは、スクリプト言語の特定のバージョンを使用してコンパイルされるため、そのバージョンがインストールされている環境でしか動作しません。
XMLをBerkeley DB XMLに挿入するには、XML形式の文字列またはファイルを用意する必要があるのでしょうか。
いいえ。文字列以外(たとえば、DOMなどのオブジェクト形式)のXMLでもそのまま挿入できます。それには、XmlEventWriterクラスを使用して、コンテンツをドキュメントに直接挿入するXML"イベント"を生成します。 これは、コンテンツをBerkeley DB XMLに挿入するもっとも効率的な方法です。
XMLの問合せ結果を処理するにはシリアライズする必要がありますか。
いいえ。XmlDocument.getContentAsEventReader()メソッドとXmlValue.asEventReader()メソッドを呼び出すと、XmlEventReaderオブジェクトが返されます。このオブジェクトを使用すれば、問合せ結果に直接アクセスできます。 これは、Berkeley DB XMLが提供するもっとも効率的なコンテンツ処理メカニズムです。
Oracle Berkeley DB XMLはXSLTをサポートしていますか。
Berkeley DB XMLにはXSLTの実装は用意されていませんが、XalanやSaxonなどのサード・パーティ製のXSLTエンジンを簡単に組み込むことができます。 複雑なXML変換処理でなければ、組込みのXQueryエンジンで十分に満足できる結果が得られることもあります。
Berkeley DB XMLは、Unicodeによるテキスト・エンコーディングをサポートしていますか。
はい、サポートしています。 XMLコンテンツには、Xercesがサポートしている任意のUnicodeエンコーディングを使用できます。 ただし、XQueryの式、ドキュメント名、索引などの文字列を受け取るインタフェースでは、UTF-8エンコーディングのみがサポートされる点に注意してください。
コンテナ内に特定のドキュメントが存在するかどうかを確認するにはどうすれば良いですか。
"このドキュメントは存在しますか"と直接尋ねるインタフェースは用意されていません。 ドキュメントが存在するかどうかを確認するには、次の方法があります。
XmlContainer::getDocument()インタフェースを使用します。以下に、C++のコード例を示します。
bool existsDoc(const std::string &docname, XmlContainer &cont) {
bool ret;
try {
XmlDocument doc = cont.getDocument(docname, DBXML_LAZY_DOCS);
ret = true;
} catch (XmlException &e) {
if (e.getExceptionCode() == XmlException::DOCUMENT_NOT_FOUND)
ret = false;
else
throw; // 不明なエラー
} return ret;
}
XmlIndexLookupオブジェクトとドキュメント名に付けられた組込みの索引を使用します。 以下に、Javaのコード例を示します。
public boolean existsDoc(String docname, XmlContainer cont,
XmlManager mgr) throws XmlException {
boolean ret = false;
XmlResults res = null;
XmlQueryContext context = null;
try {
XmlIndexLookup il = mgr.createIndexLookup(cont,
XmlManager.metaDataNamespace_uri,
XmlManager.metaDataName_name,
"metadata-equality-string",
new XmlValue(docname));
context = mgr.createQueryContext();
res = il.execute(context, new XmlDocumentConfig().setLazyDocs(true));
if (res.size() != 0)
ret = true;
} catch (XmlException e) {
if (e.getErrorCode() != XmlException.DOCUMENT_NOT_FOUND)
throw(e);
} finally {
if (res != null) res.delete();
} return ret;
}
exists(doc('mycollection.dbxml/mydocname'))XMLドキュメントを検証する方法を教えてください。
デフォルトでは、Berkeley DB XMLは、DTDまたはスキーマによるXMLドキュメントの検証を一切行いません。 検証を行うには、コンテナを作成またはオープンするときにDBXML_ALLOW_VALIDATIONフラグを指定します。これにより、コンテナに追加されるすべてのドキュメントが、ドキュメント内に指定されたDTDまたはスキーマ参照に基づいて検証されます。 DTD参照が見つからない場合、またはDTD参照がローカル・ファイル・システム上に存在するが、XmlManagerの作成時にDBXML_ALLOW_EXTERNAL_ACCESSが指定されなかった場合には、検証は実行されず、未検証(だが整形式)のドキュメントが挿入されます。 検証が期待どおりに実行されていることを確認してください。 通常のC++の検証ツールはどれでも使用できます。これらのツールをBerkeley DB XMLのイベントAPIで操作すれば、XMLドキュメントを効率的に格納および検索できます。
非常に大きなドキュメントを格納および検索する方法を教えてください。
大きなドキュメントとは、大きいために扱いにくいサイズのドキュメントのことです。 具体的にどのくらいのサイズになると"大きい"のかは明確ではありませんが、50MiBあれば間違いなく大きいと言えるでしょう。 最初に、こうした大きなドキュメントを扱いやすいサイズに分割できるかどうかを検討してください。 ここでは、分割できない場合の対処方法を説明します。 大きなドキュメントを格納するときは、次の2つの点に注意します。
出力処理についても考慮する必要があります。 大きなドキュメントの一部を検索するだけならそれほど効率は落ちませんが、ドキュメント全体をシリアライズしたり、ドキュメント全体を繰り返し処理する必要がある問合せを実行したりすると、処理速度が低下します。 こうした操作を最適化することはできません。
複数のスレッド間で共有できるオブジェクトと、共有できないオブジェクトを教えてください。
XmlManager、XmlContainer、およびXmlQueryExpressionはすべて、完全にスレッドセーフです。しかも、スレッド間で共有できるだけではありません。これらのオブジェクトを共有することで、パフォーマンスが向上し、メモリ管理も容易になります。 XmlModify(リリース2.5で廃止)とXmlIndexLookupオブジェクトは、生成された後スレッドセーフになります。つまり、この2つのオブジェクトのgetおよびsetメソッドはスレッドセーフではありませんが、これらのメソッドを使用して望みどおりにオブジェクトを設定した後は、複数のスレッドから安全にオブジェクトを実行できます。 その他のオブジェクトはスレッドセーフではないため、常に単一スレッドで使用する必要があります。 Java以外のAPIでスレッドを使用するときは、Berkeley DB環境オブジェクトをオープンするとき、必ずDB_THREADフラグを指定してください。 Javaでは、スレッド処理がデフォルトで有効になっています。
RDBMSストレージと比較したときのXMLデータベースの利点は何ですか。
XMLリポジトリがRDBMSよりも優れているのは、アプリケーション・プログラミング・モデルとデータ・ストレージ・モデルのずれが少ない点です。 特に、ドキュメントのコンテンツまたは表形式以外の情報を扱うアプリケーションでは、XMLデータベースを使用することで利益を享受できます。 スキーマなしの情報、厳密ではないがスキーマに準拠した情報、拡張可能なスキーマに準拠した情報、頻繁に変更される情報などは、XMLデータベースで扱うのに格好の情報です。 Ronald Bourretがこのトピックについて多くの記事を公開しています。
Java APIで提供されているdelete()メソッドの使用目的を教えてください。
Java APIのdelete()メソッドは、JNIによってJavaオブジェクトと関連付けられたC++リソースを解放するときに必要になります。 delete()メソッドは、オブジェクトのfinalize()メソッドによって呼び出されます(これは、Javaでfinalize()を呼び出すときに注意しなければならない点です)。 すべてのオブジェクトに対してfinalizeが呼び出される保証はありません。 Berkeley DB XMLの大半のJavaオブジェクトには、JNI経由でC++オブジェクトが関連付けられています。 Javaは、メモリが不足しそうになるとガベージ・コレクションを呼び出すことができますが、こうしたBerkeley DB XMLのJavaオブジェクトがC++オブジェクトに関連付けられているため、見た目よりもずっと大きいことは認識しません。 したがって、ガベージ・コレクションに任せずに、delete()メソッドを呼び出すようにすることは良いことです。 close()メソッドを使用してファイルをクローズするように、delete()メソッドを使用しているオブジェクトもいくつかあります。 たとえば、XmlManager、XmlContainer、XmlResultsといったオブジェクトがそうです。 これらのオブジェクトが保有するロックおよびその他のデータベース・リソースを解放するには、明示的にこれらのオブジェクトをdelete()する必要があります。 リリース2.4.xより、以下のオブジェクトに対してdelete()を呼び出す必要はなくなりました。
XmlValueXmlDocumentXmlQueryContextXmlUpdateContextXmlMetaDataXmlMetaDataIteratorXmlTransaction -- commit()またはabort()を呼び出すと削除されるコンテナの物理的な制限を教えてください。
次の3つの物理的な制約があります。
ドキュメント名に関する制限を教えてください。
Berkeley DB XMLは、ドキュメント名を、任意の長さのヌル文字で終了する単純型の文字列とみなします。 Berkeley DB XMLでは、ドキュメント名に課せられる一般的な制限はありませんが、XQuery式内でドキュメントに名前でアクセスすると問題が発生することがあります。 XQuery式では、fn:doc()を使用してドキュメントに名前を付けます。fn:doc()を呼び出すときは、URIを引数として指定します。 URIのデフォルトの形式は、"dbxml:/<container_name_or_alias>/<document_name>"です。 "dbxml:"スキームは暗黙値であり、名前の解決に使用されるデフォルトのベースURIです。 URIではパス名のデリミタとしてスラッシュ('/')を使用するため、ドキュメント名にスラッシュを使用すると問題が生じます。というのは、Berkeley DB XMLが、文字列のコンテナ名部分とドキュメント名部分を判別できなくなるからです。 コンテナ名に含まれる特殊文字を処理するには、XmlContainer::addAlias()メソッドを使用して、コンテナ名に使用する"安全な"エイリアスを作成します。 ドキュメント名には、これに相当するインタフェース(API)は用意されていません。 "myscheme://a/b/c"などのURIを使用してドキュメントに名前を付けるアプリケーションを考えてみます。 Berkeley DB XMLでは、こうしたドキュメントを問題なく作成できます。 では、アプリケーションが、XQuery式内でこのドキュメント名を参照するにはどうすれば良いでしょうか。 答えは、fn:doc()またはfn:collection()(またはその両方)に対する引数にスラッシュを使用しないようにすることです。 もっとも簡単な方法は、fn:doc()の代わりに、fn:collection()と条件を使用する方法です。 たとえば、次のような問合せを、その次の行のように書き換えることができます。
exists(doc('mycol/myscheme://a/b/c'))
exists(collection('mycol')/*[dbxml:metadata('dbxml:name')='myscheme://a/b/c'])
もちろん、ドキュメント名に含まれるスラッシュをエスケープする方法でも問題を解決できますが、そのエスケープ自体が面倒な処理です。
Berkeley DB XMLでは、どのような開発ツールを使用できますか。
Berkeley DB XMLでは、oXygen XMLエディタを組込みでサポートしています。
Java APIで提供されているdelete()メソッドとclose()メソッドの違いを教えてください。
Java APIで、delete()とclose()の両メソッドが定義されているクラスでは、この2つのメソッドの違いはなくなりました。
バンドルされていないバージョンのBerkeley DBに対してBerkeley DB XMLをビルドする方法を教えてください。
Berkeley DB XMLは、リリースによっては、Berkeley DBの特定のバージョンに依存していることがあります。 その場合は、ビルドの手順が説明されたドキュメントにその旨が明記されています。 以下にビルドの手順を示します。
UNIX
buildall.sh --with-berkeleydb=path_to_db_installationWindows Berkeley DB XMLのプロジェクト・ファイルは、Berkeley DBのヘッダー・ファイルだけでなく、ライブラリも直接参照しています。 Berkeley DBのバージョンと場所に合わせて、プロジェクト・ファイルに記述されたこれらの場所を変更する必要があります。
dbxml-2.x.x/dbxml/build_windowsにあるプロジェクト・ファイル(*.dspまたは*.vcproj)の次の箇所を変更します。
DLLの場所を指すように、PATH環境変数を必ず変更してください。Linux上でBerkeley DB XML PHPモジュールをインストールする方法を教えてください。
dbxml-2.x.y/dbxml/src/php/READMEに含まれているソース・コードに記載されている手順を参照してください。
Berkeley DB XMLのJava APIを使用しているときに起こるリンク・エラーを解決する方法を教えてください。
Berkeley DB XMLでJavaを使用していると、次のようなエラーがよく起こります。
java.lang.UnsatisfiedLinkError: no libdb_java43 in java.library.path
このエラーは通常、java.library.pathが適切に構成されていないために起こります。
java -Djava.library.path="/home/jpcs/dbxml-2.4.8/install/lib/" MyClass
SUSE 10.0で、Berkeley DBを呼び出すJavaのコード(サンプル・コードや自前のコード)を実行できないことがあります。なぜですか。
SUSE 10付属のGCCに最適化レベル'-O2'を指定して実行すると、間違ったコードが生成されます。 現在、これがデータベースを構成するときのデフォルトの設定になっています。 configureの実行後、makeの実行前に、次のコマンドを使用してMakefileを変更してください。
sed -i 's/O2/O/g' Makefile
これで、エラーは発生しなくなります。 SUSE 10付属のGCCのバージョンは次のとおりです。
gcc(GCC)4.0.2 20050901(プレリリース)(SUSE Linux)
Linuxシステムで一般的に使用可能なファイル・システムのうち、Berkeley DBのデータベース・ストレージとして最適なのはどれですか。
Linuxではどのファイル・システムを使用すべきかという質問をよく受けます。 現時点で分かっていることは、TPアプリケーションに使用するLinuxファイル・システムとしてはext2がもっともパフォーマンスが高いということです(ただし、ext2には順序付きデータ・モードが用意されていないため、信頼性に欠けます)。 次の候補はext3、最後の候補はReiserFSです。 XFSについてはパフォーマンス計測結果がありませんが、エラーが発生することを確認しています(ファイルを繰り返し拡張するアプリケーションでXFSを使用すると問題が発生しますが、Berkeley DBデータベースはまさにそうした使い方をするのが一般的です)。
WindowsでMFCを使用してプロジェクトを作成するとき、あるいはoledb.hを含むコードをコンパイルするとき、コンパイル・エラーが発生します。なぜですか。
Berkeley DBのヘッダー・ファイルdb.hとMicrosoftのヘッダー・ファイルoledb.hには、シンボルDBTYPEが定義されています。 このシンボルの使い方をどちらかのヘッダー・ファイルで変更すると、既存のコードが破壊されます。 この問題が発生したときは、まずもっとも簡単な解決方法を試してください。すなわち、どのソース・ファイルでも、この2つのヘッダー・ファイルを同時に使用することがないようにソース・コードを整理します。 つまり、Berkeley DBとMicrosoftのOLE DBライブラリの使用を切り分けて、両者がコード内で混在しないようにします。 その後、db.hまたはoledb.hのどちらかを選択して、両者を1つのソース・ファイル内で同時に使用しないようにします。 それができない場合、つまり、2つのヘッダー・ファイルを同時に使用する必要がある場合は、以下の手順に従って、いずれかの#include行をラップするようにします。 ソース・コード内のoledb.hがインクルードされている行を見つけます。 この行は自動的に生成されるstdafx.hインクルード・ファイルに含まれていることもあります。 そのヘッダー・ファイルが本当に必要かどうかを判断します。 本当に必要な場合は、インクルード行を変更します。変更前には次のようになっているとします。
#include <oledb.h>
次のとおりに変更します。
/* Berkeley DBとのDBTYPE名の衝突を避けるための回避策 */
#define DBTYPE MS_DBTYPE #include <oledb.h> #undef DBTYPE
db_cxx.h
oledb.h
db_cxx.h
oledb.h
ロッカー、ロック、またはlockオブジェクトを制限個数まで使い切ってしまった場合はどうすれば良いでしょうか。
Berkeley DBの環境では、ロッカー、ロック、およびlockオブジェクトが一定の個数を超えないかどうか監視しています。したがって、これらのリソースを使い切ってしまう可能性が常にあります。 割り当てられるロック・リソースの上限は、データベース環境の作成時に設定されます。ですから、上限値を変更するには、ロッカー、ロック、またはlockオブジェクトの数を増やして、環境を再構築する必要があります。 詳細は、Berkeley DB Programmer's Reference GuideのConfiguring locking: sizing the systemの章を参照してください。.
ディスク領域が不足するとデータベースが破損します。
Berkeley DBはディスク領域不足のエラーが発生しても稼働を続けることができますが、それには、アプリケーションでの処理をトランザクションで保護する必要があります。 アプリケーションは、更新操作をトランザクション内で実行していない場合、ディスク領域不足エラーから回復できません。そのため、ディスク領域が不足すると、データベースが破損する可能性があります。
FreeBSD 5.4で、"Fatal error 'Spinlock called when not threaded'(致命的エラー:スレッド化されていないのにスピンロックが呼び出されました)"というエラーが発生します。 どうすれば良いですか。
FreeBSD 5.4では、インストール後、次のランタイム・エラーが発生することがあります。 Fatal error 'Spinlock called when not threaded.' at line 87 in file /usr/src/lib/libpthread/thread/thr_spinlock.c (errno = 0) このエラーが発生したら、/etc/libmap.confファイルを作成して、libc_rをlibpthreadにマッピングする必要があります。 詳細な手順については、"man 4 libmap.conf"を参照してください。
Berkeley DB XMLで、"Lock table is out of available locker entries(ロック・テーブルに使用可能なロッカー・エントリがありません)"または"Lock table is out of locks(ロック・テーブルにロックがありません)"などのエラーが発生します。どうすれば良いでしょうか。
Berkeley DBの用語で"ロッカー"とは、データベース、トランザクション、カーソルなどを指します。 Berkeley DBの"ロック"は"ロッカー"によって所有され、一般に、データベースのページをロックします。 その他の種類のロックもあります。 Berkeley DB XMLでは、ロッカーはコンテナ(データベース・ハンドルを保持する)およびドキュメント(カーソルを保持する)に関連付けられます。 ロッカーとロックは、Berkeley DB環境を作成および使用している場合のみ存在します。また、アプリケーションがトランザクションを使用していない場合でも存在し、何らかのレベルの同時アクセスをサポートします。 ロッカーの数を変更するには、適切な引数を指定して環境を(再)構築します。 C++:
DbEnv::set_lk_max_lockers(u_int32_t)
DbEnv::set_lk_max_locks(u_int32_t)
Java:
EnvironmentConfig.setMaxLockers(int)
EnvironmentConfig.setMaxLocks(int)
以上の環境設定は、openメソッドを使用して環境を作成する*前*に行う必要があります。 誰しも疑問に思うのは、"どのくらいの値を上限値として設定すれば良いのか"という点です。 ロッカーについては、最低でも、同時に参照する可能性のある最大ページ数に設定する必要があります。 大規模かつ非効率的な問合せでは、コンテナ内のすべてのページを参照する可能性がありますが、その場合でも、コンテナ・サイズをページ・サイズで割った値を上限値とすれば十分なはずです。 それでも上限値に達してしまう場合は、もう一度サイズを変更してください。 既存の環境で、ロックまたはロッカー・パラメータを変更する場合は、環境をいったん削除してから再構築する必要があります。 正しく行えば、環境を再構築しても、環境内の既存のBerkeley DB XMLコンテナまたはデータベース・ファイルが影響を受けることはありません。 環境を安全に削除するには、次のどちらかの操作を実行します。
Environment.remove()(Java)またはDbEnv::remove()(C++)を呼び出します。 その後、環境をオープンしたら、environmentConfig.setAllowCreate(true)(Java)またはDB_CREATE(C++)を設定します。__db.*ファイルを削除し、アプリケーションを再起動します。 アプリケーションの再起動時に新しい環境が作成されることを確認してください。Javaアプリケーションで、"Uncaught exception from C++ API(C++ APIで発生した例外が捕捉されませんでした)"または"Out of memory(メモリ不足です)"といったエラーが表示されます。なぜですか。
これらの例外が発生する理由はいろいろ考えられますが、Javaを使用している場合はおそらく、一部のBerkeley DB XMLオブジェクトに対してdelete()メソッドを呼び出すのを忘れてしまったことが原因です。 リリース2.4より、このエラーはほとんど発生しなくなると思われます。 原因としては、XmlResultsオブジェクトを削除していないことが考えられます。 Berkeley DB XMLのすべてのJavaオブジェクトは、基盤となるC++オブジェクトに対応しています。 ある時点でJavaのガベージ・コレクタによってJavaオブジェクトが削除されると、対応するC++オブジェクトも削除されます。 しかし、Javaのガベージ・コレクタが起動されるタイミングは制御できないため、Berkeley DB XMLオブジェクトは、使い終えた時点でdelete()メソッドを使用して削除するのが賢明です(使い終えた時点で必ず削除しなければならない場合もあります)。
ソフトウェア解析ツール(Rational Software社のPurifyツールなど)の実行中に初期化されていないメモリ領域に対して読取り/書込みが実行されたというレポートが表示されます。なぜですか。
Berkeley DBでは、パフォーマンス上の理由で、データベース・ページの未使用領域に対する書込みや、未使用の構造体フィールドへの代入を実行しません。 ソフトウェア解析ツールの実行時にこれらのエラーが表示されないようにするには、--enable-umrw構成オプションを指定してビルドしてください。
"text()"を使用して問合せを実行すると遅くなります。なぜですか。
text()を問合せに使用してはなりません。 詳細は、この項目を参照してください。
コンテナ内のすべてのドキュメントを効率的に検索する方法を教えてください。
XmlContainer::getAllDocuments(0);への呼出しを使用します。これは、名前索引内のすべてのドキュメントを検索するのと同じです。 結果セットは、問合せ結果と同じように扱うことができます。たとえば、結果セットをコンテキスト項目として使用し、さらなる問合せを実行することも可能です。
アプリケーションではトランザクションを使用する必要があるのでしょうか。
トランザクションを使用する理由はいくつかあります。以下のいずれもトランザクションを必要とする十分な理由になります。
以下の場合はTDSを使用する必要はありません。
Berkeley DBとBerkeley DB XMLの間でトランザクションを共有する方法を教えてください。
Berkeley DB XMLのトランザクション用メソッドはすべて、XmlTransactionオブジェクトを引数として受け取ります。 このオブジェクトは、作成済みのBerkeley DBトランザクション・オブジェクトを使用して作成できます。 また、Berkeley DB XMLのXmlTransactionオブジェクトからBerkeley DBオブジェクトを取得することもできます。 ただし、互いに衝突する可能性のある2つの無関係なトランザクション・オブジェクトを作成しないように注意する必要があります。 C++で、DbTxnからXmlTransactionオブジェクトを作成するコードは、次のようになります。
DbTxn *dbtxn = 0; // Berkeley DB環境を使用してトランザクションを開始する
env.txn_begin(0, &dbtxn, 0); // DbTxnからXmlTransactionを作成する
XmlTransaction xmltxn = manager.createTransaction(dbtxn);
C++で、XmlTransactionからDbTxnオブジェクトを取得するコードは、次のようになります。
XmlTransaction xmltxn = manager.createTransaction(); //DbTxnを取得する
DbTxn *dbtxn = xmltxn.getDbTxn();
DbTxnとXmlTransactionを一緒に使用する際に、注意しなければならない点がいくつかあります。 トランザクションをコミットまたは中断すると、基盤となるDbTxnオブジェクトも削除されます。削除されたDbTxnオブジェクトを再度参照してはなりません。 また、1つのDbTxnオブジェクトを同時に複数のXmlTransactionオブジェクトから参照することも許されません。
リリース2.5以降のBerkeley DB XMLでは、Berkeley DB C++ APIではなくBerkeley DB C APIを使用しています。そのため、DbTxnの代わりにDB_TXNを、getDbTxn()の代わりにgetDB_TXN()を使用してください。
Javaで、TransactionオブジェクトからXmlTransactionオブジェクトを作成するコードは、次のようになります。
Transaction dbtxn = env.beginTransaction(null,null);
XmlTransaction xmltxn = manager.createTransaction(dbtxn);
XmlTransactionオブジェクトからTransactionオブジェクトを取得するコードは、次のようになります。
XmlTransaction xmltxn = manager.createTransaction();
Transaction dbtxn = xmltxn.getTransaction();
デッドロックの検出を行う必要はありますか。
デッドロックの処理は、トランザクションを使用するアプリケーション、およびBerkeley DB XMLに対して読取り/書込みアクセス、または2つの書込みアクセスを同時に実行するアプリケーションにとって必須です。 詳細については、Berkeley DB Programmer's Reference Guideのこことここを参照してください。 Javaで、デッドロックを処理する方法を以下に示します。
public interface SleepycatXmlTransactionWrapper<T> {
public T run() throws Exception;
}
public class SleepycatXmlTransaction {
public static final int DEADLOCK_RETRIES = 3;
private static ThreadLocal<XmlTransaction> tx = new ThreadLocal<XmlTransaction>();
// 静的メソッドでThreadLocal XmlTransactionを使用する
private static void createTransaction() {/* 省略 */}
private static void commitTransaction() {/* 省略 */}
private static void abortTransaction() {/* 省略 */}
public static <T> T wrapWithDeadlockRetry(
SleepycatXmlTransactionWrapper<T> txw) throws Exception {
T returnValue = null;
for(int i = 0; i < DEADLOCK_RETRIES; i++) {
try {
createTransaction();
// 処理を行い、必要な値を返す
returnValue = txw.run();
commitTransaction();
return returnValue;
}
catch (Exception e) {
// 中断する。ただし、トランザクションでデッドロックが
// 発生した場合は、ループを続行する
abortTransaction();
if(isDeadlock(e)) {
Loggers.XML_RUNTIME.info("Transaction deadlock " + (i + 1));
// 他のスレッドに委ねる
Thread.yield();
continue;
}
else
throw e;
}
}
// リトライ回数が上限を超えた場合
throw new OutOfRetriesException("Transaction failed after " +
DEADLOCK_RETRIES + " retries");
}
}
SleepycatXmlTransactionWrapperを実装したメソッド・ローカルなクラスを作成し、静的メソッドSleepycatXmlTransaction.wrapWithDeadlockTransaction(...)に渡すことによって使用する
public String myMethod(final String bar) throws SomeException {
String result = null;
try {
result = SleepycatXmlTransaction.wrapWithDeadlockRetry(
new SleepycatXmlTransactionWrapper<String>() {
public String run() throws Exception {
//...コードをすべてここに記述する
}
});
}
catch(Exception any) {
}
return result;
}
自前のXQuery関数を書くことはできますか。
XQuery関数は、複雑な再帰的問合せの実行に必要不可欠です。 これを実行する構文は次のとおりです。
declare function local:times_two($value) { $value * 2 }
XQueryでは、関数の宣言を問合せのプロローグ(問合せの先頭)に含めるように規定されています。
1つの問合せ内に複数のコンテナを指定する方法を教えてください。
XQuery式内で複数のfn:collection()関数を使用できます。 たとえば、次のように論理和演算子("|")を使用して、fn:collection()に対する異なる呼出しの結果をまとめることができます。
(collection("A") | collection("B") | collection("C"))/foo/bar
データベースに格納せずにドキュメントに問合せを発行する方法を教えてください。
以下に、3つの方法を示します。
XmlManagerの作成時にDBXML_ALLOW_EXTERNAL_ACCESSフラグを指定し、 XQuery関数fn:doc()を使用するとき、"file://"または"http://"URLを参照します。XmlDocumentオブジェクトを作成し、 そのXmlDocumentオブジェクトからXmlValueを作成します。そして、問合せを実行するとき、XmlValue値をXmlQueryExpression::execute()のcontextItemパラメータとして指定します。
XmlDocument doc = manager.createDocument();
doc.setContent(myXmlContent);
XmlValue val(doc);
expression.execute(val, queryContext);
これにより、XMLドキュメントを、問合せ内でコンテキスト項目(".")として参照できます。
XmlResolverからクラスを導出して、任意のURIを正しい場所(ディスク上、メモリ内など)に解決することもできます。 これは、自作のカスタムXmlResolverをXmlManagerオブジェクトに登録することで実現できます。XQuery式内でXQueryモジュールを参照するには、どうすれば良いですか。
XQueryモジュールのimport文は、たとえば、import module namespace tm='test-module' at 'test-module.xq';のように指定します。 Berkeley DB XMLのデフォルトのモジュール解決では、"test-module.xq"をファイル・システム内のパス名とみなします。 ですから、上の文は、現在のディレクトリ内でtest-module.xqファイルを検索します。 モジュール解決では、問合せに使用するXmlQueryContextオブジェクトに設定されているベースURIも参照します。 たとえば、ベースURIが"file://tmp/"の場合、モジュール解決では、"/tmp/test-module.xq"ファイルが検索されます。モジュールをインポートする別の方法もあります。すなわち、XmlResolverクラスのインスタンスを実装し、それをXmlManager::registerResolver()メソッドを使用して登録する方法です。 モジュールをインポートすると、XmlResolver::resolveEntity()メソッドが呼び出されます。 これにより、モジュールの場所を完全に制御できるだけでなく、ファイル・システム内、Berkeley DBデータベース内、またはBerkeley DB XMLメタデータ項目内にモジュールを配置できます。また、コード内でモジュールを作成することも可能です。
class testResolver extends XmlResolver {
public testResolver() throws XmlException {}
public boolean resolveDocument(XmlTransaction txn, XmlManager mgr,
String uri, XmlValue val)
throws XmlException {
return false;
}
public boolean resolveCollection(XmlTransaction txn, XmlManager mgr,
String uri, XmlResults res)
throws XmlException {
return false;
}
public XmlInputStream resolveSchema(XmlTransaction txn, XmlManager mgr,
String location, String nameSpace)
throws XmlException {
return null;
}
public XmlInputStream resolveEntity(XmlTransaction txn, XmlManager mgr,
String systemId, String publicId)
throws XmlException {
return null;
}
public boolean resolveModuleLocation(XmlTransaction txn, XmlManager mgr,
String nameSpace, XmlResults result)
throws XmlException {
return false;
}
public XmlInputStream resolveModule(XmlTransaction txn, XmlManager mgr,
String moduleLocation, String nameSpace)
throws XmlException {
return null;
}
}
resolveModule()メソッド内で個々のXQueryモジュールを返す場合は、ファイルから、またはXmlManager.createInputStream()を使用してjava.io.InputStreamオブジェクトからXmlInputStreamオブジェクトを作成します。同じ処理を実行するC++のコードも、Javaのコードと同様です。
ノードの値を取得する方法を教えてください。
/foo/bar/text()という構文を使用する人が多いようですが、 たいていの場合、この構文を使用するのは間違いです。 以下に、その理由を説明します。 次のドキュメントで考えてみます。
<foo>
<bar>hello <baz>you</baz>there</bar>
</foo>
XQueryでは、text()はテキスト・ノード・テストです。 つまり、/foo/bar/text()の例では、text()はchild::text()の略です。略さずに書くと、何をしているのか少しは分かります。 この式は、現在のコンテキストのすべてのテキストの子ノードを返します。 したがって、このドキュメントでは、上の式は、2つのテキスト・ノードを返します。すなわち、"hello"という値を持つテキスト・ノードと"there"という値を持つテキスト・ノードです。 ここで重要なのは、テキスト・ノード(文字列ではない)が返されるという点だけではなく、返されたテキスト・ノードの値を連結しても、bar要素の値とは一致しないという点です。 XQueryの仕様では、bar要素の文字列値は"hello you there"として定義されます。 つまり、子孫テキスト・ノードのすべての値を連結したものです。 もう1つの重要な問題は、属性ノードが子テキスト・ノードを持たないという点です。 ですから、属性値を取得するつもりで/foo/@bar/text()と書いても、空のシーケンスが返されて驚くだけです(これは、問合せエンジンにしてみればまったく正しい処理です)。 また、Berkeley DB XMLの問合せプランナは、text()が使用されていても一切最適化を行いません。 最適化できないのです。というのは、Berkeley DB XMLの索引は、要素と属性の子テキスト・ノードではなく、それらの値に対して作成されているからです。 したがって、text()を使用すると、貴重な最適化の機会を失うことになります。 /foo/bar/text()という構文が間違っている理由はこのくらいにして、 ノードの値を取得する方法を説明しましょう。 以下に、いくつかの方法と、それらの違いを示します。
fn:string()関数を使用します。 この関数は、ノードの文字列値を返します。スキーマ・タイプ情報は返しません。fn:data()関数を使用します。 この関数は、ノードの型付き値を返します。つまり、ドキュメントにスキーマが存在する場合は、そのスキーマに記述されている型の値を返します。 スキーマが存在しない場合は、xdt:untypedAtomic型の値を返します。/foo/barをxs:decimalまたはxs:date(/foo/bar)にキャスト)。 この方法は、特定の型の値を取得するときに使用します。どの方法を使用する場合でも、何をしているのか正確に理解せず、安易にtext()を使用することは避けてください。
問合せ内にドキュメント名を含めるにはどうすれば良いですか。
方法は2つあります。
dbxml:" URIスキームで、doc()関数を使用します。 dbxml URIスキームの形式は"dbxml:/<container_name_or_alias>(/<document_name>)?"です。通常、次のように使用します。
doc("dbxml:/myContainer/myDocument")
デフォルトのベースURIは"dbxml:/"です。したがって、URIのスキーム識別子は省略してもかまいません。 doc("myContainer/myDocument")
dbxml:metadata()関数を使用します。
for $a in collection() return dbxml:metadata("dbxml:name", $a)
メタデータを問い合わせるにはどうすれば良いですか。
ドキュメントのメタデータを問合せで使用するには、dbxml:metadata()関数を使用します。 この関数は、第1引数としてメタデータの名前(文字列)を、第2引数(オプション)として、メタデータが検索されるドキュメントのノードを受け取ります。 第2引数を省略すると、コンテキスト項目が使用されます。 dbxml:metadata()関数は、第1引数をQNameとみなし、そのプリフィックスをURIとして解決することによって、ドキュメントの型付きのメタデータを返します。 以下に、いくつか使用例を挙げます。 次の例は、デフォルトのコレクションに格納されているすべてのドキュメントの名前を返します。
for $a in collection() return dbxml:metadata("dbxml:name", $a)
次の例は、{http://timstamp.org}timestampメタデータが5未満のドキュメントを返します。
declare namespace ts = "http://timestamp.org";
collection()[dbxml:metadata("ts:timestamp") < 5]
xs:QName型の値の使い方を教えてください。
問合せ内でxs:QName項目を作成する方法はいくつかあります。
xs:QName()コンストラクタ関数を使用すると、問合せ内でQNameを作成できます。 その場合は、使用するプリフィックスの名前空間URIがバインドされていることを確認する必要があります。
declare namespace foo="http://foo"; xs:QName("foo:bar")
xs:QName()関数を使用します。 これにより、QNameの名前空間URI、およびプリフィックスとローカル名を指定できます。
xs:QName("http://foo", "foo:bar")
ドキュメントを更新または変更する方法を教えてください。
以下に、3つの方法を示します。
XmlDocument::setContent()またはXmlDocument::setContentAsXmlInputStream()を使用してドキュメントのコンテンツを変更し、 XmlContainer::updateDocument()を使用して、変更内容をXmlContainerに戻します。XmlModifyクラスを使用して更新を指定することもできますが、それは避けて、XQuery Updateを使用するようにしてください。XML:DB InitiativeのXUpdate仕様はサポートされていますか。
いいえ。XUpdate仕様は不完全であり、Berkeley DB XMLが依拠している重要な概念(トランザクションなど)がサポートされていません。 また、2000年9月14日以来変更されておらず、草案のままです。 Berkeley DB XMLでは、バージョン2.4より、W3CのXQuery Update言語をサポートしています。永続的なXMLドキュメントの更新には、こちらのほうがずっと適しています。
ドキュメントの削除に、ドキュメントの挿入と同じくらいの時間がかかるのはなぜですか。
Berkeley DB XMLのデータ・モデルでは、ドキュメントから索引エントリへのマッピングを格納しないことで、データベース領域(および更新時間)を節約しています。 このため、削除時には、削除する索引エントリを算出するために、ドキュメントを再解析する必要があります。
XmlModifyを呼び出すと、"Cannot perform a modification on an XmlValue that isn't either Node or Document(ノードでもドキュメントでもないXmlValueを変更することはできません)"というエラーが発生します。 何が問題なのでしょうか。
このエラーは通常、変更対象として選択した値が、XmlModify::execute()に渡されたXmlDocumentと同じものではないことを示しています。 この判断は、データベースから2つの異なる方法で取得した同じドキュメントではなく、参照の等価性に基づいて行われます。 たとえば、次のコードはエラーになります。
modify.addRemoveStep(manager.prepare("doc('container/foo')//bar", qc));
XmlDocument foo = container.getDocument("foo");
modify.execute(XmlValue(foo), qc, uc);
ところが、次のコードはエラーになりません。
modify.addRemoveStep(manager.prepare(".//bar", qc));
XmlDocument foo = container.getDocument("foo");
modify.execute(XmlValue(foo), qc, uc);
変更手順の問合せでは常に相対指定を使用するというのが、簡単な基本原則です。つまり、fn:doc()やfn:collection()などを使用せずに、コンテキスト項目(".")を基準としてパスを指定する必要があります。
Berkeley DB XMLシェルでコマンドライン編集を行うにはどうすれば良いでしょうか。
rlwrapという便利なプログラムがあります。 このプログラムは、コマンドライン履歴の表示機能やbashスタイルのコマンドライン編集機能を備えています。 次のように使用します。
rlwrap dbxml [ここにコマンドライン引数]
部分文字列索引の使用方法を教えてください。また、どのようなときに使用すれば良いのでしょうか。
深くネストされた(場合によっては14階層以上)複雑なDTDを使用してマークアップされたドキュメント・セットについて考えてみましょう。 ユーザーの視点から見て典型的な"シンプルな"検索とは、ドキュメント内で単語FOOを検索せよ、といったものです。 これを実行するもっとも直感的な問合せは次のようになります。 collection()//*[contains(., 'FOO')] このケースを最適化する索引タイプは存在するでしょうか。 Berkeley DB XMLでは、名前付きノードの値に対してのみ索引を作成するため、現時点では、上記のような種類の問合せを最適化できません。 最適化するには、ドキュメントの要素に部分文字列索引を追加して、次のような複合問合せを書くしかありません。
collection()/docElem[contains(., 'FOO')]//*[contains(., 'FOO')]
Berkeley DB XML索引の中身を確認する方法を教えてください。
Berkeley DB XMLでは、XmlIndexLookupオブジェクトとそのメソッドを使用して、索引に直接アクセスする方法を用意しています。 XmlIndexLookupオブジェクトを使用すると、次の操作を実行できます。
次に、C++による簡単な等価性検索の例を示します。
// contとmgrはオープンしたコンテナおよびマネージャとする。
// "年齢"要素に対する10進数型の等価性索引の検索を実行する。
// 年齢が27才と等しいすべてのエントリを検索する。
// 演算子は指定していない。値を指定した場合のデフォルトの演算子は"等価"となる。
XmlIndexLookup il = mgr.createIndexLookup(cont, "", age,
"node-element-equality-decimal",
XmlValue(XmlValue::DECIMAL, 27));
// 通常の問合せコンテキスト
XmlQueryContext qc = mgr.createQueryContext();
// 検索を実行する
XmlResults res = il.execute(qc);
// 結果を処理する
次に示す例は少し複雑です。この例では、上の例と同じ索引("年齢"に対する10進数の索引)を使用していますが、12才より上で35才以下のすべてのエントリを検索し、逆順にソートして(もっとも高い年齢を最初にして)返しています。
XmlQueryContext qc = mgr.createQueryContext();
// 基本のXmlIndexLookupオブジェクトを作成する。このオブジェクトで
// 下限の値と演算子(12、より大きい)を設定する
XmlIndexLookup il = mgr.createIndexLookup(cont, "", age,
"node-element-equality-decimal",
XmlValue(XmlValue::DECIMAL, 12),
XmlIndexLookup::GT);
// 上限の値とその演算子(35、以下)を設定する
il.setHighBound(XmlValue(XmlValue::DECIMAL, 35),
XmlIndexLookup::LTE);
// 逆順フラグを設定して演算を実行する。
XmlResults res = il.execute(qc, DBXML_REVERSE_ORDER);
// 結果を処理する
このインタフェースは、(単独で、またはXQueryインタフェースと組み合わせて)索引を直接検索することでメリットが得られる高度なアプリケーションで使用すると便利です。
エッジ型索引で両方の名前を指定するにはどうすれば良いですか。
多くのユーザーが、エッジ型索引に"親/子"の名前の組合せを指定しようとしますが、Berkeley DB XMLでは、そのような指定はできません。 エッジ型索引は親の名前だけを記録します。Berkeley DB XMLでは、特定の名前の親を持つノードに対してのみ索引エントリを作成するように指定することはできません。
索引を追加する最善の方法を教えてください。
Berkeley DB XMLでは、ドキュメントが挿入される前にコンテナに索引を指定するのが一番です。 ドキュメントが挿入されているコンテナの索引を変更するには、そのコンテナ内のすべてのドキュメントに付けられた索引を作成し直す必要があるため、かなり時間がかかる可能性があります。 後で索引を変更する必要がある場合は、索引を作成し直すコストが分割されて軽減されるように、一度に複数の索引を追加または削除するのが最善の方法です。 つまり、次のようなコードを実行します。
// 索引の仕様を取得する
XmlIndexSpecification ispec = container.getIndexSpecification();
// 索引の仕様を変更する
// 索引の仕様を再設定する
container.setIndexSpecification(ispec, ...);
dbxmlシェル・プログラムを使用して1つの索引を追加または削除するのは簡単ですが、dbxmlシェルは一度に1つの索引しか操作できないため、ドキュメントが格納されたコンテナの索引を管理する場合は、コードを書いたほうがおそらく効率的です。
Eclipse 3.3とBerkeley DB XMLを使用して、Javaで開発する方法を教えてください。
Java APIをインストールして、Eclipse内にJavaプロジェクトを作成した後、次の手順に従って、プロジェクトのビルド・パスにdb.jarとdbxml.jarを追加します。
db.jarとdbxml.jarを追加します。これらのjarファイルは、(dbxml-root)/jarフォルダに格納されています。Berkeley DB XMLのJavaアプリケーションをEclipse 3.3でデバッグする方法を教えてください。
デバッグ時にBerkeley DB XMLのJavaライブラリに入るためには、次の手順に従って、Berkeley DB XMLのJavaソースをリンクする必要があります。
(BDB XML-root)/dbxml/src/java」を選択します。Eclipse環境内でBerkeley DB XMLのJavaアプリケーションをデバッグするとき、ネイティブのC++ライブラリ内にステップインするにはどうすれば良いのでしょうか。
ネイティブのC++ライブラリ内にステップインするには、Berkeley DB XML、Windows、およびVisual Studioのソースをインストールする必要があります。 以下の説明では、Visual Studio 2005 Express Editionを使用するものとします。 具体的なプロセスについては、この ブログ に動画を交えたレポートが掲載されていますが、Berkeley DB XMLでこのプロセスを実行するには、いくつか手順を追加する必要があります。 Visual StudioとEclipseを組み合わせて使用するには、まず、正しい引数を指定してC++ライブラリをビルドする必要があります。 最初に、ソリューション全体のビルド・モードをDebugに設定します。 次に、「dbxml_java」プロジェクトを右クリックして、メニューから「Properties」を選択します。 Propertiesウィンドウで「Linker」→「Debugging」を選択し、Generate Map FileをYes(/MAP)に、Map ExportsをYes(/MAPINFO:EXPORTS)に設定します。 XercesLib、xqilla、dbxml、db_javaの各プロジェクトについて、同じ手順を繰り返します。 すべて設定したら、これらのライブラリをすべて再構築します。 次に、ご使用のコンピュータのPATHからBerkeley DB XMLライブラリのすべてのリリース用.dllファイルを削除します(libdbxml24.dllは削除するが、libdbxml24d.dllは残すなど)。 たとえば、リリース用.dllファイルをdbxml/binに格納している場合は、それらのファイルをすべてHideDllという名前のフォルダに移動します。 dbxmlプロジェクトをビルドするたびに、リリース用.dllファイルがdbxml/binに戻される点に注意してください。 リリース用.dllがPathに残ったままになっていると、無効なメモリ・アクセス例外、およびヒープ内の無効なメモリを削除したことによる例外が発生します。 というのは、リリース用ライブラリとデバッグ用ライブラリは、混在させることができない異なる方法でメモリにアクセスしますが、その両者がともにロードされてしまうからです。 安全のため、dbxml/build_windows/Releaseディレクトリ(すべてのリリース用ライブラリが格納されているディレクトリ)を削除しておいたほうが良いでしょう。 次に、以下の手順に従って、Javaプログラムがサーバーとして実行されるようにVisual Studioをセットアップします。 まず、「dbxml_java」プロジェクトを右クリックして、メニューから「Properties」を選択します。 Propertiesウィンドウで「Configuration Properties」→「Debugging」を選択します。 Commandに、java実行可能ファイルのパス、たとえばC:\jdk1.5.0\bin\java.exeを設定し、Command Argumentsセクションに次のように入力します。
-Xmx400m -Xms400m -Xdebug -Xnoagent \
-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 \
-classpath <paths-to-jar-files> <java-program-to-run>
たとえば、コンピュータ上でテスト・プログラムAutoOpenTest.javaを実行するには、次のように入力します。
-Xmx400m -Xms400m -Xdebug -Xnoagent \
-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 \
-classpath C:\Sleepycat\jar\junit-4.4.jar;C:\Sleepycat\jar\dbxmltest.jar;\
C:\Sleepycat\jar\dbxml.jar;C:\Sleepycat\jar\db.jar \
dbxmltest.XmlTestRunner
次に、Working Directoryに作業ディレクトリとして使用するディレクトリを設定します。 「OK」をクリックして、設定した内容を保存します。 「dbxml_java」を右クリックして、「Debug」→「Start New Instance」を選択します。 これにより、Javaプログラムがサーバーとして起動され、Eclipseからのアタッチを待機する状態に入ります。実行が開始されるのはEclipseにアタッチされてからです。 次に、Eclipseに移動し、Javaプログラム内にブレーク・ポイントを設定して、 「Run」→「Open Debug Dialog」を選択します。 「Remote Java Application」を選択して、新規アプリケーションを作成します。 Connection TypeにStandard(Socket Attach)、Hostにlocalhost、Portに8000を設定します。「Debug」をクリックすると、デバッガが待機中のJavaサーバーにアタッチされます。 Java内にブレーク・ポイントが設定されていれば、プログラムはEclipse内で停止します。C++内にブレーク・ポイントが設定されていれば、Visual Studio内でも停止します。 JavaからC++、またはC++からJavaに直接ステップインすることはできませんが、JavaからXmlContainer_putDocument__SWIG_1に入りたい場合は、Visual Studioでdbxml_java_wrap.cppに移動し、Java_com_sleepycat_dbxml_dbxml_1javaJNI_XmlContainer_1putDocument_1_1SWIG_10関数(または、SWIGによってXmlContainer_putDocument__SWIG_1への呼出しに応答することを許可された任意の関数)内にブレーク・ポイントを設定してください。 Eclipseの起動時に、"MSVCP80D.dllファイルが見つかりません"といった類のエラー・メッセージが表示されることがあります。これは、Windowsが当該ファイルを見つけることができないのではなく、.dllファイルの構成方法に誤りがあるためです。 このようなエラーが発生したら、.dllファイルが正しくビルドおよび構成されるように、dbxml/bin/debugディレクトリとdbxml/build_windows/Win32/Debugディレクトリを削除して、再度ビルドを実行してください。