Java Data Objects(JDO)は、Java でデータベースにアクセスするための標準インターフェースであり、Java のクラスとデータベースのテーブルとの間のマッピングを提供します。Datastore で JDO を使用するために利用できるオープンソースのプラグインがあります。このページでは、そのプラグインを使う方法について説明します。
警告: 多くのデベロッパーは、低水準の Datastore API を使用するか、Datastore 専用に開発された Objectify などのオープンソースの API を使用するほうが快適に作業できると思われます。JDO は、従来型のリレーショナル データベースに使用するように設計されているため、リレーショナル データベースとは異なる Datastore の特性(エンティティ グループや祖先クエリなど)を明示的に表す方法がありません。これは、理解や修正が困難な細かい問題の原因になることがあります。
App Engine Java SDK には、App Engine 用 DataNucleus プラグインのバージョン 2.x が含まれています。このプラグインは、DataNucleus Access Platform のバージョン 3.0 に対応しているため、JDO 3.0 経由で App Engine Datastore を使用できます。
JDO の詳細は、Access Platform 3.0 のドキュメントをご覧ください。特に、JDO マッピングと JDO API をご覧ください。
警告: App Engine 用 DataNucleus プラグイン 2.x は DataNucleus バージョン 3.x を使用します。この新しいプラグインには、以前の(1.x)プラグインとの完全な下位互換性はありません。新しいバージョンにアップグレードする場合は、アプリケーションの更新とテストを実行するようにしてください。
JDO 2.x および 3.0 をサポートするビルドツール
Maven を使用して、App Engine に DataNucleus プラグインのバージョン 2.x または 3.0 を使用できます。
- Maven ユーザーの場合:
pom.xml
ファイルで次のように構成することで、クラスを拡張できます。<plugin> <groupId>org.datanucleus</groupId> <artifactId>maven-datanucleus-plugin</artifactId> <version>3.2.0-m1</version> <configuration> <api>JDO</api> <props>${basedir}/datanucleus.properties</props> <verbose>true</verbose> <enhancerName>ASM</enhancerName> </configuration> <executions> <execution> <phase>process-classes</phase> <goals> <goal>enhance</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.datanucleus</groupId> <artifactId>datanucleus-core</artifactId> <version>3.1.3</version> </dependency> </dependencies> </plugin>
DataNucleus プラグインのバージョン 2.x への移行
このセクションでは、DataNucleus Access Platform 3.0 と JDO 3.0 に対応する App Engine 用 DataNucleus プラグインのバージョン 2.x を使用するように、アプリをアップグレードする手順について説明します。このプラグインには、1.x プラグインとの完全な下位互換性はなく、変更される可能性があります。アップグレードする場合は、アプリケーションのコードを更新してテストするようにしてください。
新しいデフォルト動作
App Engine DataNucleus プラグインのバージョン 2.x には、以前のバージョンとは異なるデフォル動作が設定されています。
PersistenceManager.makePersistent()
とPersistenceManager.deletePersistent()
への非トランザクション呼び出しがアトミックに実行されるようになりました(以前は、これらの関数が次のトランザクションまたはPersistenceManager.close()
で実行されていました)。- PersistenceManagerFactory(PMF)の割り当てが重複しても例外が発生しなくなりました。代わりに、永続プロパティの
datanucleus.singletonPMFForName
を true に設定しておくと、その名前が現在割り当てられているシングルトン PMF が返されるようになります。 - 非所有関係がサポートされるようになりました。非所有関係を参照してください。
構成ファイルの変更
アプリケーションを App Engine DataNucleus プラグインのバージョン 2.x にアップグレードするには、build.xml
と jdoconfig.xml
の構成の設定を変更する必要があります。
警告: 構成の更新後には、アプリケーション コードをテストして、下位互換性が確保されるようにする必要があります。新しいアプリケーションをセットアップしていて最新バージョンのプラグインを使用する場合は、JDO 3.0 のセットアップに進んでください。
PersistenceManagerFactoryClass
プロパティが変更されました。jdoconfig.xml
内の次の行を見つけます。<property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
変更後の内容:
<property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
build.xml
での設定
DataNucleus 2.x に対応させるには、copyjars
ターゲットを変更する必要があります。
copyjars
ターゲットが変更されました。次のセクションを探します。 変更後の内容:<target name="copyjars" description="Copies the App Engine JARs to the WAR."> <mkdir dir="war/WEB-INF/lib" /> <copy todir="war/WEB-INF/lib" flatten="true"> <fileset dir="${sdk.dir}/lib/user"> <include name="**/*.jar" /> </fileset> </copy> </target>
<target name="copyjars" description="Copies the App Engine JARs to the WAR."> <mkdir dir="war/WEB-INF/lib" /> <copy todir="war/WEB-INF/lib" flatten="true"> <fileset dir="${sdk.dir}/lib/user"> <include name="**/appengine-api-1.0-sdk*.jar" /> </fileset> <fileset dir="${sdk.dir}/lib/opt/user"> <include name="appengine-api-labs/v1/*.jar" /> <include name="jsr107/v1/*.jar" /> <include name="datanucleus/v2/*.jar" /> </fileset> </copy> </target>
datanucleusenhance
ターゲットが変更されました。次のセクションを探します。 変更後の内容:<target name="datanucleusenhance" depends="compile" description="Performs enhancement on compiled data classes."> <enhance_war war="war" /> </target>
<target name="datanucleusenhance" depends="compile" description="Performs enhancement on compiled data classes."> <enhance_war war="war"> <args> <arg value="-enhancerVersion"/> <arg value="v2"/> </args> </enhance_war> </target>
JDO 3.0 のセットアップ
データストアへのアクセスに JDO を使用するには、App Engine アプリで次のことを行う必要があります。
- JDO の JAR と DataNucleus プラグインは、アプリの
war/WEB-INF/lib/
ディレクトリに配置する必要があります。 jdoconfig.xml
という名前の JDO に App Engine データストアの使用を指示する構成を持つ構成ファイルが、アプリのwar/WEB-INF/classes/META-INF/
ディレクトリに必要です。- プロジェクトのビルドプロセスで、コンパイル後の「拡張」手順をコンパイル済みのデータクラスに対して実行して、JDO 実装に関連付ける必要があります。
JAR のコピー
JDO とデータストアの JAR は App Engine Java SDK に含まれています。これらは appengine-java-sdk/lib/opt/user/datanucleus/v2/
ディレクトリにあります。
JAR をアプリケーションの war/WEB-INF/lib/
ディレクトリにコピーします。
appengine-api.jar
が war/WEB-INF/lib/
ディレクトリにあることも確認します(プロジェクトの作成時にすでにコピーしている場合もあります)。App Engine DataNucleus プラグインは、この JAR を使用してデータストアにアクセスします。
jdoconfig.xml ファイルの作成
JDO インターフェースでは、アプリケーションの war/WEB-INF/classes/META-INF/
ディレクトリに jdoconfig.xml
という名前の構成ファイルが必要です。このファイルは、この場所に直接作成することも、ビルドプロセスでソース ディレクトリからコピーすることもできます。
作成するファイルには次の内容を含めます。
<?xml version="1.0" encoding="utf-8"?> <jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig"> <persistence-manager-factory name="transactions-optional"> <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/> <property name="javax.jdo.option.ConnectionURL" value="appengine"/> <property name="javax.jdo.option.NontransactionalRead" value="true"/> <property name="javax.jdo.option.NontransactionalWrite" value="true"/> <property name="javax.jdo.option.RetainValues" value="true"/> <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/> <property name="datanucleus.appengine.singletonPMFForName" value="true"/> </persistence-manager-factory> </jdoconfig>
Datastore の読み取りポリシーと呼び出し期限の設定
Datastore のクエリページで説明したように、Datastore の動作は、読み取りポリシー(強整合性と結果整合性)と呼び出し期限を設定することでカスタマイズできます。JDO では、<persistence-manager-factory>
ファイルの jdoconfig.xml
要素に目的の値を指定することで、このカスタマイズを実施します。ある PersistenceManager
インスタンスで行われるすべての呼び出しには、このマネージャーが PersistenceManagerFactory
によって作成されたときの実際の構成値が使用されます。また、これらの値は 1 つの Query
オブジェクトごとに設定をオーバーライドすることもできます。
PersistenceManagerFactory
の読み取りポリシーを設定するには、名前が datanucleus.appengine.datastoreReadConsistency
のプロパティを含めます。有効な値は、EVENTUAL
と STRONG
です。未指定の場合のデフォルトは STRONG
です(ただし、これらの設定は、特定のエンティティ グループ内の祖先クエリにのみ適用される点に注意してください。祖先以外のクエリは、有効な読み取りポリシーの有無にかかわらず、常に結果整合性になります)。
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
読み取りと書き込みに個別のデータストア呼び出し期限を設定できます。読み取りには、JDO の標準プロパティ javax.jdo.option.DatastoreReadTimeoutMillis
を使用します。書き込みには、javax.jdo.option.DatastoreWriteTimeoutMillis
を使用します。値は、ミリ秒単位の時間です。
<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
クロスグループ(XG)トランザクションを使用する場合は、次に示すプロパティを追加します。
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
異なる name
属性を使用して、同じ jdoconfig.xml
ファイルに複数の <persistence-manager-factory>
要素を設定し、同じアプリで異なる構成の PersistenceManager
インスタンスを使用できます。たとえば、次の jdoconfig.xml
ファイルは、"transactions-optional"
という名前と "eventual-reads-short-deadlines"
という名前の 2 つの構成を確立します。
<?xml version="1.0" encoding="utf-8"?> <jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig"> <persistence-manager-factory name="transactions-optional"> <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/> <property name="javax.jdo.option.ConnectionURL" value="appengine"/> <property name="javax.jdo.option.NontransactionalRead" value="true"/> <property name="javax.jdo.option.NontransactionalWrite" value="true"/> <property name="javax.jdo.option.RetainValues" value="true"/> <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/> </persistence-manager-factory> <persistence-manager-factory name="eventual-reads-short-deadlines"> <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/> <property name="javax.jdo.option.ConnectionURL" value="appengine"/> <property name="javax.jdo.option.NontransactionalRead" value="true"/> <property name="javax.jdo.option.NontransactionalWrite" value="true"/> <property name="javax.jdo.option.RetainValues" value="true"/> <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/> <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" /> <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" /> <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" /> <property name="datanucleus.singletonPMFForName" value="true" /> </persistence-manager-factory> </jdoconfig>
名前付きの構成セットで PersistenceManager
を作成する方法については、後述の PersistenceManager インスタンスの取得をご覧ください。
データクラスの拡張
JDO はビルドプロセスでコンパイル後の「拡張」手順を使用して、データクラスを JDO の実装に関連付けます。
次に示すコマンドを使用して、コンパイルしたクラスに対する拡張手順をコマンドラインから実行できます。
java -cp classpath com.google.appengine.tools.enhancer.Enhance class-files
classpath には、appengine-java-sdk/lib/
ディレクトリの JAR appengine-tools-api.jar
とすべてのデータクラスが含まれている必要があります。
DataNucleus のバイトコード エンハンサの詳細については、DataNucleus のドキュメントをご覧ください。
PersistenceManager インスタンスの取得
アプリは、PersistenceManager クラスのインスタンスを使用して JDO を操作します。このインスタンスを取得するには、PersistenceManagerFactory クラスのインスタンスでメソッドをインスタンス化して呼び出します。ファクトリは、JDO 構成を使用して PersistenceManager のインスタンスを作成します。
PersistenceManagerFactory インスタンスの初期化には時間がかかるため、1 つのアプリは 1 つのインスタンスを再利用します。PersistenceManagerFactory インスタンスは、次に示すように、静的なインスタンスでシングルトン ラッパークラスを作成することで管理が簡単になります。
PMF.java
import javax.jdo.JDOHelper; import javax.jdo.PersistenceManagerFactory; public final class PMF { private static final PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional"); private PMF() {} public static PersistenceManagerFactory get() { return pmfInstance; } }
ヒント: "transactions-optional"
は、jdoconfig.xml
ファイル内の構成セットの名前を参照します。アプリで複数の構成セットを使用する場合は、このコードを拡張して、必要に応じて JDOHelper.getPersistenceManagerFactory()
を呼び出すことをおすすめします。このコードにより、各 PersistenceManagerFactory
のシングルトン インスタンスがキャッシュに保存されます。
アプリは、ファクトリ インスタンスを使用して、データストアにアクセスするリクエストごとに 1 つの PersistenceManager インスタンスを作成します。
import javax.jdo.JDOHelper; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; import PMF; // ... PersistenceManager pm = PMF.get().getPersistenceManager();
PersistenceManager は、データ オブジェクトの保存、更新、削除と、データストア クエリの実行に使用します。
PersistenceManager インスタンスの使用が終了したら、その close()
メソッドを呼び出す必要があります。PersistenceManager インスタンスをその close()
メソッドの呼び出し後に使用するとエラーが発生します。
try { // ... do stuff with pm ... } finally { pm.close(); }
サポートされていない JDO 3.0 の機能
次に示す JDO インターフェースの機能は、App Engine の実装ではサポートされていません。
- 多対多の所有関係。
- 「結合」クエリ。親の種類に対してクエリを実行する場合は、フィルタで子エンティティのフィールドを使用することはできません。親の関係フィールドは、キーを使用することで、クエリで直接テストすることができます。
- JDOQL のグループ化とその他の集約クエリ。
- ポリモーフィック クエリ。クラスのクエリを実行して、サブクラスのインスタンスを取得することはできません。各クラスは、データストア内の個別のエンティティの種類として表されます。
トランザクションの無効化と既存の JDO アプリの移植
JDO 構成では、datanucleus.appengine.autoCreateDatastoreTxns
というプロパティを true
に設定することをおすすめします。これは App Engine 固有のプロパティであり、データストア トランザクションをアプリケーション コード内で管理される JDO トランザクションに関連付けるように JDO の実装に指示するためのものです。新しいアプリを初めからビルドする場合は、このようにするとよいでしょう。ただし、App Engine での実行が必要な既存の JDO ベースのアプリケーションがある場合は、このプロパティの値を false
に設定する別の永続構成の使用が必要になることもあります。
<?xml version="1.0" encoding="utf-8"?> <jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig"> <persistence-manager-factory name="transactions-optional"> <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/> <property name="javax.jdo.option.ConnectionURL" value="appengine"/> <property name="javax.jdo.option.NontransactionalRead" value="true"/> <property name="javax.jdo.option.NontransactionalWrite" value="true"/> <property name="javax.jdo.option.RetainValues" value="true"/> <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="false"/> </persistence-manager-factory> </jdoconfig>
この設定が役立つ理由について理解するには、1 つのトランザクション内では同じエンティティ グループに属するオブジェクトにのみオペレーションを実行できることを思い出してください。従来型のデータベースを使用するようにビルドされたアプリケーションは、通常、トランザクション内で任意のレコードのセットを更新できる、グローバル トランザクションを使用できることを想定しています。App Engine データストアはグローバル トランザクションをサポートしていないため、グローバル トランザクションが利用できると想定しているコードでは例外がスローされます。コードベース全体(おそらくは巨大)を見直してトランザクション管理コードをすべて削除する代わりに、単にデータストアのトランザクションを無効にすることができます。これは、複数レコードの変更のアトミック性を想定しているコードに対処することにはなりませんが、アプリを動作させることはできます。それで、トランザクション コードのリファクタリングを一度に全部行うのではなく、段階的に必要に応じて実行できるようになります。