App Engine での JPA の使用

Java Persistence API(JPA)は、Java でデータベースにアクセスするための標準インターフェースであり、Java のクラスとデータベースのテーブルとの間のマッピングを自動的に実行します。データストアで JPA を使用するために利用できるオープンソースのプラグインがあります。このページでは、そのプラグインを使う方法について説明します。

警告: 多くのデベロッパーは、低水準の Datastore API を使用するか、Datastore 専用に開発された Objectify などのオープンソースの API を使用するほうが快適に作業できると思われます。JPA は、従来型のリレーショナル データベースで使用するように設計されているため、リレーショナル データベースとは異なるデータストアのいくつかの特性(エンティティ グループや祖先クエリなど)を明示的に表すことができません。これは、理解や修正が困難な細かい問題の原因になることがあります。

App Engine Java SDK には、Datastore の DataNucleus プラグインのバージョン 2.x が含まれています。このプラグインは、DataNucleus Access Platform のバージョン 3.0 に対応しているため、JPA 2.0 経由で App Engine Datastore を使用できるようになります。

JPA の詳細については、Access Platform 3.0 のドキュメントをご覧ください。特に、JPA のドキュメントJPA API をご覧ください。

警告: App Engine DataNucleus プラグインのバージョン 2.x では、DataNucleus v3.x を使用しています。2.x プラグインには、以前の 1.x バージョンとの下位互換性はありません。新しいバージョンにアップグレードする場合は、アプリケーションの更新とテストを実行するようにしてください。

JPA 2.x と 3.0 をサポートするビルドツール

Apache Ant または Maven を使用して、App Engine に DataNucleus プラグインのバージョン 2.x または 3.0 を使用できます。

  • Ant ユーザーの場合: SDK には、拡張手順を実行するための Ant タスクが含まれています。プロジェクトを設定するときには、JAR をコピーして、構成ファイルを作成する必要があります。
  • 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-api-jdo</artifactId>
                            <version>3.1.3</version>
                        </dependency>
                    </dependencies>
                </plugin>

DataNucleus プラグインのバージョン 2.x への移行

このセクションでは、アプリをアップグレードして App Engine 用 DataNucleus プラグインのバージョン 2.x を使用できるようにする手順について説明します。このバージョンは DataNucleus Access Platform 3.0 と JPA 3.0 に対応しています。2.x プラグインにはバージョン 1.x との完全な下位互換性はありません。また、このプラグインは予告なく変更される場合があります。アップグレードする場合は、アプリケーションのコードを更新してテストするようにしてください。

新しいデフォルト動作

App Engine DataNucleus プラグインのバージョン 2.x には、以前のバージョンとは異なるデフォルトの動作が設定されています。

  • JPA の「永続性プロバイダ」は org.datanucleus.api.jpa.PersistenceProviderImpl になりました。
  • Level2 キャッシングがデフォルトで有効になっています。以前のデフォルト動作に戻すには、永続性プロパティ datanucleus.cache.level2.type を none に設定します。または、クラスパスに datanucleus-cache プラグインを含め、永続性プロパティ datanucleus.cache.level2.type を javax.cache に設定して、L2 キャッシングに Memcache を使用できるようにします。
  • データストア IdentifierFactory のデフォルト設定は datanucleus2 になりました。以前の動作に戻すには、永続性プロパティ datanucleus.identifierFactory を datanucleus1 に設定します。
  • EntityManager.persist()EntityManager.merge()EntityManager.remove() への非トランザクション呼び出しがアトミックに実行されるようになりました。以前は、次のトランザクションまたは EntityManager.close() で実行されていました。
  • JPA で retainValues が有効になっています。そのため、読み込まれたフィールドの値が、コミットの後もオブジェクト内に保持されます。
  • javax.persistence.query.chunkSize は使用しないことになりました。かわりに datanucleus.query.fetchSize を使用してください。
  • EMF の割り当てが重複しても例外がスローされなくなりました。永続性プロパティ datanucleus.singletonEMFForName を true に設定すると、その名前が現在割り当てられているシングルトン EMF が返されるようになります。
  • 非所有関係がサポートされるようになりました。
  • データストア識別子がサポートされるようになりました。

新機能の詳細なリストについては、リリースノートを参照してください。

設定ファイルの変更

アプリケーションを App Engine DataNucleus プラグインのバージョン 2.0 にアップグレードするには、build.xmlpersistence.xml の設定の一部を変更する必要があります。新しいアプリケーションをセットアップしていて、最新バージョンのプラグインを使用する場合は、JPA 2.0 のセットアップに進んでください。

警告 設定の更新後には、アプリケーション コードをテストして、下位互換性が確保されるようにする必要があります。

Ibuild.xml

DataNucleus 2.x に対応させるには、copyjars ターゲットを変更する必要があります。

  1. 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>
  2. 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>

persistence.xml

<provider> ターゲットが変更されました。次のセクションを探します。

        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>

このセクションを次のように変更します。

        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>

JPA 2.0 のセットアップ

データストアへのアクセスに JPA を使用するには、App Engine アプリケーションで次のことを行う必要があります。

  • JPA およびデータストアの JAR を、アプリケーションの war/WEB-INF/lib/ ディレクトリに配置します。
  • persistence.xml という設定ファイルをアプリの war/WEB-INF/classes/META-INF/ ディレクトリに配置し、App Engine Datastore を使用するよう JPA に指示する設定を行う必要があります。
  • プロジェクトの構築プロセスで、コンパイル済みのデータクラスでコンパイル後の「拡張」手順を実行し、そのデータクラスを JPA 実装に関連付けます。

JAR のコピー

JPA とデータストアの JAR は、App Engine Java SDK に含まれています。これらの JAR は appengine-java-sdk/lib/opt/user/datanucleus/v2/ ディレクトリにあります。

JAR をアプリケーションの war/WEB-INF/lib/ ディレクトリにコピーします。

appengine-api.jar ディレクトリには war/WEB-INF/lib/ も配置してください(プロジェクトの作成時にすでにコピーしている場合もあります)。App Engine DataNucleus プラグインは、この JAR を使用してデータストアにアクセスします。

persistence.xml ファイルの作成

JPA インターフェースには、アプリケーションの persistence.xml ディレクトリ内にある war/WEB-INF/classes/META-INF/ という設定ファイルが必要です。このファイルは、この場所に直接作成することも、ビルドプロセスでソース ディレクトリからコピーすることもできます。

作成するファイルには次の内容を含めます。

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
            <property name="datanucleus.singletonEMFForName" value="true"/>
        </properties>

    </persistence-unit>

</persistence>

読み取りポリシーとデータストア呼び出し期限

Datastore クエリページで説明したように、読み取りポリシー(強整合性と結果整合性)と、persistence.xml ファイルの EntityManagerFactory のデータストア呼び出し期限を設定できます。これらの設定は、<persistence-unit> 要素に追加します。特定の EntityManager インスタンスで行われるすべての呼び出しは、マネージャが EntityManagerFactory で作成されたときに選択された設定を使用することになります。また、Query オブジェクトごとにこれらのオプションをオーバーライドすることもできます(詳しくは後述)。

読み取りポリシーを設定するには、datanucleus.appengine.datastoreReadConsistency という名前のプロパティを追加します。指定できる値は、EVENTUAL(結果整合性による読み取り)と STRONG(強整合性による読み取り)です。指定しない場合、デフォルトで STRONG が適用されます。

            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

読み取りと書き込みに個別のデータストア呼び出し期限を設定できます。読み取りには、JPA の標準プロパティ javax.persistence.query.timeout を使用します。書き込みには、datanucleus.datastoreWriteTimeout を使用します。値は、ミリ秒単位の時間です。

            <property name="javax.persistence.query.timeout" value="5000" />
            <property name="datanucleus.datastoreWriteTimeout" value="10000" />

クロスグループ(XG)トランザクションを使用する場合は、次に示すプロパティを追加します。

            <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

異なる name 属性を使用すると、複数の <persistence-unit> 要素を同じ persistence.xml ファイルに格納することができ、同じアプリ内で異なる設定の EntityManager インスタンスを使用できるようになります。たとえば、次に示す persistence.xml ファイルでは、"transactions-optional""eventual-reads-short-deadlines" という名前の 2 組の設定が規定されています。

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>

    <persistence-unit name="eventual-reads-short-deadlines">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>

            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
            <property name="javax.persistence.query.timeout" value="5000" />
            <property name="datanucleus.datastoreWriteTimeout" value="10000" />
            <property name="datanucleus.singletonEMFForName" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

構成セットの名前を指定して EntityManager を作成する方法については、後述の EntityManager インスタンスの取得をご覧ください。

Query オブジェクトごとに読み取りポリシーと呼び出し期限をオーバーライドできます。Query の読み取りポリシーをオーバーライドするには、次のように setHint() メソッドを呼び出します。

        Query q = em.createQuery("select from " + Book.class.getName());
        q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

上述したように、指定できる値は "EVENTUAL""STRONG" です。

読み取りタイムアウトをオーバーライドするには、次のように setHint() を呼び出します。

        q.setHint("javax.persistence.query.timeout", 3000);

キーでエンティティを取得する場合は、これらのオプションの設定をオーバーライドすることはできません。

データクラスの拡張

JPA の DataNucleus 実装では構築プロセスにおいてコンパイル後の「拡張」手順を使用し、データクラスと JPA 実装を関連付けます。

次に示すコマンドを使用して、コンパイルしたクラスに対する拡張手順をコマンドラインから実行できます。

java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer
class-files

classpath には、すべてのデータクラスに加え、appengine-java-sdk/lib/tools/ ディレクトリの JAR datanucleus-core-*.jardatanucleus-jpa-*datanucleus-enhancer-*.jarasm-*.jargeronimo-jpa-*.jar* は各 JAR の所定のバージョン番号)が含まれている必要があります。

DataNucleus のバイトコード エンハンサの詳細については、DataNucleus のドキュメントを参照してください。

EntityManager インスタンスの取得

アプリケーションは、EntityManager クラスのインスタンスを使用して JPA を操作します。このインスタンスを取得するには、EntityManagerFactory クラスのインスタンスでメソッドをインスタンス化して呼び出す必要があります。ファクトリはJPA 設定("transactions-optional" という名前で識別)を使用して EntityManager インスタンスを作成します。

EntityManagerFactory インスタンスは初期化に時間がかかるため、アプリケーションではできるだけ 1 つのインスタンスを再利用するのがよいでしょう。簡単なのは、次に示すようにシングルトン ラッパー クラスを静的なインスタンスで作成する方法です。

EMF.java

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

    public static EntityManagerFactory get() {
        return emfInstance;
    }
}

ヒント: "transactions-optional"は、persistence.xml ファイル内の構成セットの名前です。アプリケーションで複数の設定を使用している場合は、このコードを拡張し、必要に応じて Persistence.createEntityManagerFactory() を呼び出します。このコードにより、各 EntityManagerFactory のシングルトン インスタンスがキャッシュされます。

アプリケーションはファクトリ インスタンスを使用して、データストアにアクセスするリクエストごとに 1 つの EntityManager インスタンスを作成します。

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import EMF;

// ...
    EntityManager em = EMF.get().createEntityManager();

EntityManager を使用して、データ オブジェクトを格納、更新、削除したり、データストア クエリを実行することができます。

EntityManager インスタンスの使用後は、このインスタンスの close() メソッドを呼び出す必要があります。close() メソッドを呼び出した後で EntityManager インスタンスを使用するとエラーになります。

    try {
        // ... do stuff with em ...
    } finally {
        em.close();
    }

クラスとフィールドのアノテーション

JPA で保存した各オブジェクトが App Engine データストアのエンティティになります。エンティティの種類は、クラスの簡単な名前(パッケージ名を除いたもの)から派生します。クラスの各永続化フィールドがエンティティのプロパティを表しており、プロパティの名前はフィールドの名前と同じ(大文字と小文字もそのまま)です。

データストアでの Java クラスの格納および取得に JPA を使用できるように宣言するには、そのクラスに @Entity アノテーションを指定します。次に例を示します。

import javax.persistence.Entity;

@Entity
public class Employee {
    // ...
}

データストアに格納するデータクラスのフィールドは、デフォルトで永続的な型にするか、永続化を明示的に宣言する必要があります。DataNucleus のウェブサイトに、JPA のデフォルトの永続化動作の詳細を記載した表があります。フィールドの永続化を明示的に宣言するには、@Basic アノテーションを設定します。

import java.util.Date;
import javax.persistence.Enumerated;

import com.google.appengine.api.datastore.ShortBlob;

// ...
    @Basic
    private ShortBlob data;

次のフィールドの型を使用できます。

  • データストアでサポートされる主な型
  • コア データストア型の値のコレクション(java.util.List<...> など)
  • @Entity クラスのインスタンス、またはインスタンスのコレクション
  • 埋め込みクラス(エンティティのプロパティとして格納)

データクラスには、パブリックまたは保護されたデフォルトのコンストラクタと、対応するデータストア エンティティの主キーを格納するための専用のフィールドが 1 つ必要です。このフィールドは、4 種類のキーフィールドから選択でき、種類ごとに異なる値の型とアノテーションを使用します(詳細については、データの作成: キーをご覧ください)。最も簡単なキー フィールドは長整数型の値です。この場合、オブジェクトがデータストアに初めて保存されるときに、クラスの他のインスタンス全体で一意の値が JPA によって自動的に設定されます。長整数型のキーでは、@Id アノテーションと、@GeneratedValue(strategy = GenerationType.IDENTITY) アノテーションを使用します。

import com.google.appengine.api.datastore.Key;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// ...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

データクラスの例を次に示します。

import com.google.appengine.api.datastore.Key;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String firstName;

    private String lastName;

    private Date hireDate;

    // Accessors for the fields. JPA doesn't use these, but your application
    does.

    public Key getKey() {
        return key;
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Date getHireDate() {
        return hireDate;
    }
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }
}

継承

JPA では、継承を使用するデータクラスを作成できます。ここでは JPA 継承が App Engine でどのように動作するかを説明しますが、その前にこのトピックに関する DataNucleus のドキュメントをお読みになることをおすすめします。読み終えたら、先に進みましょう。App Engine での JPA 継承は、DataNucleus のドキュメントに記載されているとおりに動作しますが、制限がいくつ設けられています。ここでは、この制限について説明したうえで、具体的な例をいくつか挙げていきます。

「JOINED」(結合)の継承方法では、1 つのデータ オブジェクトのデータを複数の「テーブル」に分割することが可能ですが、App Engine データストアでは結合がサポートされていません。このため、この継承方法でデータ オブジェクトを操作するには、継承のレベルごとにリモート プロシージャ コールが必要になります。しかし、これは実に非効率的なので、「JOINED」の継承方法はデータクラスではサポートされていません。

次に、「SINGLE_TABLE」(単一テーブル)の継承方法では、データ オブジェクトのデータを、継承階層のルートにある永続化クラスに関連付けられている 1 つの「テーブル」に格納できます。この方法は非効率的ではありませんが、現在サポートされていません。この方法については、今後のリリースで検討していきます。

「TABLE_PER_CLASS」(クラスごとのテーブル)と「MAPPED_SUPERCLASS」(マップされたスーパークラス)の方法は、DataNucleus のドキュメントに記載されているとおりに動作します。例を見てみましょう。

Worker.java

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@Entity
@MappedSuperclass
public abstract class Worker {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Key key;

    private String department;
}

Employee.java

// ... imports ...

@Entity
public class Employee extends Worker {
    private int salary;
}

Intern.java

import java.util.Date;
// ... imports ...

@Entity
public class Intern extends Worker {
    private Date internshipEndDate;
}

この例では、@MappedSuperclass アノテーションを Worker クラス宣言に追加しました。これにより、JPA で Worker のすべての永続化フィールドが、そのサブクラスのデータストア エンティティに格納されます。Employee インスタンスを指定して persist() を呼び出すことで作成されたデータストア エンティティには、「department」と「salary」の 2 つのプロパティがあります。Intern インスタンスを指定して persist() を呼び出すことで作成されたデータストア エンティティには、「department」と「internshipEndDate」の 2 つのプロパティがあります。データストアには種類「Worker」のエンティティは含まれません。

ここで、もう少し面白いことをしてみましょう。たとえば、EmployeeIntern の他に、退職した従業員を記述する Employee を特別に設定する必要があるとします。

FormerEmployee.java

import java.util.Date;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
// ... imports ...

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class FormerEmployee extends Employee {
    private Date lastDay;
}

この例では、@Inheritance アノテーションを FormerEmployee クラス宣言に追加し、strategy 属性を InheritanceType.TABLE_PER_CLASS に設定しました。これにより、FormerEmployee のすべての永続化フィールドとそのスーパークラスが、FormerEmployee インスタンスに対応するデータストア エンティティに格納されます。FormerEmployee インスタンスを指定して persist() を呼び出すことで作成されたデータストア エンティティには、「department」、「salary」、「lastDay」の 3 つのプロパティがあります。FormerEmployee に対応する種類「Employee」のエンティティはありませんが、ランタイム型が Employee のオブジェクトを指定して persist() を呼び出すと、種類「Employee」のエンティティが作成されます。

関係フィールドの宣言された型が、これらのフィールドに割り当てたオブジェクトのランタイム型と一致している場合は、関係と継承を組み合わせて利用できます。詳細については、ポリモーフィック関係のセクションをご覧ください。このセクションでは JDO の例が示されていますが、概念と制約は JPA でも同じです。

サポートされていない JPA 2.0 の機能

JPA インターフェースの次の機能は、App Engine 実装ではサポートされていません。

  • 多対多の所有関係。
  • 「結合」クエリ。親の種類に対してクエリを実行する場合は、フィルタで子エンティティのフィールドを使用することはできません。キーを使用して、親の関係フィールドを直接クエリでテストすることはできます。
  • 集約クエリ(group by、having、sum、avg、max、min)。
  • ポリモーフィック クエリ。クラスのクエリを実行して、サブクラスのインスタンスを取得することはできません。各クラスは、データストア内の個別のエンティティの種類として表されます。
このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java の App Engine スタンダード環境