JPA mit App Engine verwenden

Die Java Persistence API (JPA) ist eine Standardschnittstelle für den Zugriff auf Datenbanken in Java und bietet eine automatische Zuordnung zwischen Java-Klassen und Datenbanktabellen. Für die Verwendung von JPA mit Datastore steht ein Open-Source-Plug-in zur Verfügung. Mit den Informationen auf dieser Seite soll Ihnen der Einstieg in dieses Plug-in erleichtert werden.

Warnung: Wir sind der Meinung, dass für die meisten Entwickler die Low-Level Datastore API oder eine der speziell für Datastore entwickelten Open Source APIs wie Objectify besser geeignet ist. JPA wurde für die Verwendung mit herkömmlichen relationalen Datenbanken entwickelt und bietet daher keine Möglichkeit, einige Aspekte von Datastore explizit darzustellen, in denen sich Datastore von herkömmlichen relationalen Datenbanken unterscheidet. Hierzu zählen zum Beispiel Entitätengruppen und Ancestor-Abfragen. Dies kann zu komplexen Problemen führen, die nur schwer nachvollzogen und behoben werden können.

Das App Engine Java SDK enthält die Version 2.x des DataNucleus-Plug-ins für Datastore. Dieses Plug-in entspricht Version 3.0 der DataNucleus Access Platform, mit der Sie den App Engine-Datenspeicher über JPA 2.0 verwenden können.

Weitere Informationen zu JPA finden Sie in der Dokumentation zu Access Platform 3.0. Lesen Sie insbesondere die JPA-Dokumentation.

Warnung: Die Version 2.x des DataNucleus-Plug-ins für App Engine verwendet DataNucleus Version 3.x. Das 2.x-Plug-in ist mit dem vorherigen 1.x-Plug-in nicht vollständig abwärtskompatibel. Wenn Sie ein Upgrade auf die neue Version ausführen, sollten Sie unbedingt Ihre Anwendung aktualisieren und testen.

Build-Tools mit Unterstützung für JPA 2.x und 3.0

Zur Verwendung von Version 2.x oder 3.0 des DataNucleus-Plug-ins für App Engine können Sie Apache Ant oder Maven einsetzen:

  • Für Ant-Nutzer: Das SDK enthält eine Ant-Aufgabe, die den Optimierungsschritt ausführt. Sie müssen die JARs kopieren und die Konfigurationsdatei erstellen, wenn Sie Ihr Projekt einrichten.
  • Für Maven-Nutzer: Sie können die Klassen mit folgenden Konfigurationen in Ihrer pom.xml-Datei optimieren:
                <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>

Zu Version 2.x des DataNucleus-Plug-ins migrieren

Mit der Anleitung in diesem Abschnitt können Sie Ihre Anwendung zur Verwendung von Version 2.x des DataNucleus-Plug-ins für App Engine aktualisieren. Diese Version entspricht DataNucleus Access Platform 3.0 und JPA 2.0. Die 2.x-Plug-in-Version ist mit Version 1.x nicht vollständig abwärtskompatibel und kann jederzeit ohne Ankündigung geändert werden. Wenn Sie ein Upgrade ausführen, sollten Sie Ihren Anwendungscode unbedingt aktualisieren und testen.

Neue Standardverhaltensweisen

Version 2.x des DataNucleus-Plug-ins für App Engine weicht in einigen Standardeinstellungen von der Vorgängerversion 1.x ab:

  • Der "Persistenzanbieter" für JPA ist jetzt org.datanucleus.api.jpa.PersistenceProviderImpl.
  • Level2-Caching ist standardmäßig aktiviert. Setzen Sie das Persistenzattribut datanucleus.cache.level2.type auf none, wenn Sie zum vorherigen Standardverhalten zurückkehren möchten. Alternativ können Sie das datanucleus-cache-Plug-in im classpath einfügen und das Persistenzattribut datanucleus.cache.level2.type auf javax.cache setzen, um Memcache für das L2-Caching zu verwenden.
  • Datastore IdentifierFactory ist jetzt standardmäßig datanucleus2. Setzen Sie das Persistenzattribut datanucleus.identifierFactory auf datanucleus1, wenn Sie zum vorherigen Verhalten zurückkehren möchten.
  • Nicht transaktionale Aufrufe an EntityManager.persist(), EntityManager.merge()und EntityManager.remove()werden jetzt atomar ausgeführt. Zuvor erfolgte die Ausführung bei der nächsten Transaktion oder nach EntityManager.close().
  • Für JPA ist retainValues aktiviert. Das bedeutet, dass die Werte geladener Felder nach einem Commit in Objekten beibehalten werden.
  • javax.persistence.query.chunkSize wird nicht mehr unterstützt. Verwenden Sie stattdessen datanucleus.query.fetchSize.
  • Bei doppelten EMF-Zuordnungen wird keine Ausnahme mehr ausgelöst. Wenn Sie das Persistenzattribut datanucleus.singletonEMFForName auf true festgelegt haben, wird die aktuell zugeordnete Singleton-EMF für den jeweiligen Namen zurückgegeben.
  • Beziehungen ohne Abhängigkeiten werden jetzt unterstützt.
  • Datastore Identity wird jetzt unterstützt.

Eine vollständige Liste der neuen Funktionen finden Sie unter Versionshinweise.

Änderungen an den Konfigurationsdateien

Wenn Sie Ihre Anwendung auf Version 2.0 des DataNucleus-Plug-ins für App Engine umstellen möchten, müssen Sie einige Konfigurationseinstellungen in build.xml und persistence.xml ändern. Wenn Sie eine neue Anwendung einrichten und die neueste Version des DataNucleus-Plug-ins verwenden möchten, fahren Sie mit JPA 2.0 einrichten fort.

Achtung! Nachdem Sie die Konfiguration aktualisiert haben, müssen Sie Ihren Anwendungscode testen, um Abwärtskompatibilität zu gewährleisten.

In build.xml

Zur Anpassung von DataNucleus 2.x muss das Ziel copyjars geändert werden:

  1. Das Ziel copyjars hat sich geändert. Ändern Sie den folgenden Abschnitt:
      <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>

    nach
      <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. Das Ziel datanucleusenhance hat sich geändert. Ändern Sie den folgenden Abschnitt:
      <target name="datanucleusenhance" depends="compile"
          description="Performs enhancement on compiled data classes.">
        <enhance_war war="war" />
      </target>

    nach
      <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>

In persistence.xml

Das Ziel <provider> hat sich geändert. Ändern Sie den folgenden Abschnitt:

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

in:

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

JPA 2.0 einrichten

App Engine-Anwendungen benötigen folgende Elemente, um JPA für den Zugriff auf den Datenspeicher zu verwenden:

  • Die JPA- und Datenspeicher-JARs müssen im Verzeichnis war/WEB-INF/lib/ der Anwendung vorhanden sein.
  • Eine Konfigurationsdatei mit dem Namen "persistence.xml" muss im Verzeichnis war/WEB-INF/classes/META-INF/ der Anwendung vorhanden sein, und zwar mit einer Konfiguration, die JPA anweist, den App Engine Datastore zu verwenden.
  • Beim Erstellungsprozess des Projekts muss nach der Kompilierung ein "Optimierungsschritt" für die kompilierten Datenklassen ausgeführt werden, um sie der JPA-Implementierung zuzuordnen.

JARs kopieren

Die JPA- und Datenspeicher-JARs sind im App Engine Java-SDK enthalten. Sie können sie im appengine-java-sdk/lib/opt/user/datanucleus/v2/-Verzeichnis finden.

Kopieren Sie die JARs in das Verzeichnis war/WEB-INF/lib/ Ihrer Anwendung.

Achten Sie darauf, dass sich die Datei appengine-api.jar ebenfalls im Verzeichnis war/WEB-INF/lib/ befindet. Möglicherweise haben Sie die Datei bereits beim Erstellen des Projekts kopiert. Das DataNucleus-Plug-in für App Engine greift mithilfe dieser JAR-Datei auf den Datenspeicher zu.

Datei persistence.xml erstellen

Für die JPA-Schnittstelle ist eine Konfigurationsdatei mit dem Namen "persistence.xml" im Verzeichnis war/WEB-INF/classes/META-INF/ der Anwendung erforderlich. Sie können diese Datei direkt in diesem Verzeichnis erstellen oder sie von Ihrem Erstellungsprozess aus einem Quellverzeichnis in dieses Verzeichnis kopieren lassen.

Erstellen Sie die Datei mit folgendem Inhalt:

<?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>

Leserichtlinie und Zeitlimit für den Aufruf des Datenspeichers

Wie auf der Seite Datastore-Abfragen beschrieben, können Sie die Leserichtlinie (Strong Consistency gegenüber Eventual Consistency) und das Zeitlimit für den Datenspeicheraufruf für eine EntityManagerFactory in der Datei persistence.xml festlegen. Diese Einstellungen werden in das <persistence-unit>-Element aufgenommen. Alle mit einer bestimmten EntityManager-Instanz ausgeführten Aufrufe verwenden die Konfiguration, die beim Erstellen des Managers durch die EntityManagerFactory ausgewählt wurde. Sie können diese Optionen auch für eine einzelne Query überschreiben (unten beschrieben).

Nehmen Sie zur Festlegung der Leserichtlinie das Attribut mit dem Namen "datanucleus.appengine.datastoreReadConsistency" mit auf. Die möglichen Werte sind EVENTUAL (für Lesevorgänge mit Eventual Consistency) und STRONG (für Lesevorgänge mit Strong Consistency). Wenn keine Angabe erfolgt, lautet der Standardwert STRONG.

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

Sie können für Lese- und Schreibvorgänge jeweils separate Zeitlimits für Datastore-Aufrufe festlegen. Verwenden Sie für Lesevorgänge das JPA-Standardattribut javax.persistence.query.timeout. Verwenden Sie für Schreibvorgänge datanucleus.datastoreWriteTimeout. Der Wert ist ein Zeitraum in Millisekunden.

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

Wenn Sie gruppenübergreifende Transaktionen (XG-Transaktionen) verwenden möchten, fügen Sie folgende Property hinzu:

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

Sie können mehrere Elemente <persistence-unit> in dieselbe Datei persistence.xml aufnehmen und dabei unterschiedliche name-Attribute verwenden, um EntityManager-Instanzen mit verschiedenen Konfigurationen in derselben Anwendung zu nutzen. Die folgende Datei persistence.xml erstellt beispielsweise zwei Konfigurationssätze, einen mit dem Namen ""transactions-optional"" und einen weiteren mit dem Namen ""eventual-reads-short-deadlines"":

<?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>

Informationen zum Erstellen eines EntityManager mit einem benannten Konfigurationssatz finden Sie im Abschnitt EntityManager-Instanz beschaffen weiter unten.

Sie können die Leserichtlinie und das Zeitlimit für den Aufruf eines einzelnen Query-Objekts überschreiben. Rufen Sie zum Überschreiben der Leserichtlinien für ein Query die zugehörige setHint()-Methode so auf:

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

Wie oben erwähnt, sind die möglichen Werte "EVENTUAL" und "STRONG".

Wenn Sie das Lesezeitlimit überschreiben möchten, rufen Sie wie setHint()so auf:

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

Es gibt keine Möglichkeit zum Überschreiben der Konfiguration für diese Optionen, wenn Sie Entitäten anhand ihres Schlüssels abrufen.

Datenklassen optimieren

Die DataNucleus-Implementierung von JPA bedient sich nach der Kompilierung eines "Optimierungsschritts" im Erstellungsprozess, um der JPA-Implementierung Datenklassen zuzuordnen.

Sie können den Optimierungsschritt für kompilierte Klassen mit folgendem Befehl über die Befehlszeile ausführen:

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

Der Klassenpfad muss die JARs datanucleus-core-*.jar, datanucleus-jpa-*, datanucleus-enhancer-*.jar, asm-*.jar und geronimo-jpa-*.jar – dabei steht * für die entsprechende Versionsnummer der einzelnen JAR-Dateien – aus dem appengine-java-sdk/lib/tools/-Verzeichnis sowie sämtliche Datenklassen enthalten.

Weitere Informationen zur DataNucleus-Bytecode-Optimierung finden Sie in der DataNucleus-Dokumentation.

EntityManager-Instanz beschaffen

Anwendungen interagieren über eine Instanz der EntityManager-Klasse mit JPA. Sie erhalten diese Instanz durch Instanziieren und Aufrufen einer Methode für eine Instanz der EntityManagerFactory-Klasse. Die Factory-Instanz verwendet die JPA-Konfiguration (gekennzeichnet durch den Namen ""transactions-optional""), um EntityManager-Instanzen zu erstellen.

Da die Initialisierung einer EntityManagerFactory-Instanz einige Zeit in Anspruch nimmt, sollten Sie eine einzelne Instanz so oft wie möglich wiederverwenden. Eine einfache Methode hierfür besteht darin, wie folgt eine Singleton-Wrapper-Klasse mit einer statischen Instanz zu erstellen:

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;
    }
}

Tipp: "transactions-optional" bezieht sich auf den Namen der in der Datei persistence.xml festgelegten Konfiguration. Wenn Ihre Anwendung mehrere Konfigurationssets verwendet, müssen Sie diesen Code erweitern, um Persistence.createEntityManagerFactory() wie gewünscht aufzurufen. Ihr Code sollte eine Singleton-Instanz jeder EntityManagerFactory-Instanz im Cache speichern.

Die Anwendung verwendet die Factory-Instanz zum Erstellen einer einzigen EntityManager-Instanz für jede Anfrage, die auf den Datenspeicher zugreift.

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

import EMF;

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

Sie verwenden den EntityManager zum Speichern, Aktualisieren und Löschen von Datenobjekten sowie zum Ausführen von Datenspeicherabfragen.

Wenn Sie mit der EntityManager-Instanz fertig sind, müssen Sie die zugehörige close()-Methode aufrufen. Es ist ein Fehler, die EntityManager-Instanz nach dem Aufrufen ihrer close()-Methode zu verwenden.

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

Klassen- und Feldannotationen

Jedes von JPA gespeicherte Objekt wird zu einer Entität im App Engine-Datenspeicher. Der Typ der Entität wird aus dem einfachen Namen der Klasse abgeleitet (ohne Paketnamen). Jedes persistente Feld der Klasse stellt eine Property der Entität dar. Dabei ist der Name der Property gleich dem Namen des Felds (unter Beibehaltung der Groß- und Kleinschreibung).

Wenn Sie eine Java-Klasse so deklarieren möchten, dass sie mit JPA im Datenspeicher gespeichert und daraus abgerufen werden kann, vergeben Sie für die Klasse eine @Entity-Annotation. Beispiel:

import javax.persistence.Entity;

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

Felder der Datenklasse, die im Datenspeicher gespeichert werden sollen, müssen entweder einen Typ aufweisen, der standardmäßig persistiert wird, oder explizit als persistent deklariert werden. Auf der DataNucleus-Website finden Sie eine Übersicht über das Standardverhalten der JPA-Persistenz. Wenn Sie ein Feld explizit als persistent deklarieren möchten, vergeben Sie dafür eine @Basic-Annotation:

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

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

// ...
    @Basic
    private ShortBlob data;

Felder können folgende Typen haben:

  • Einen der vom Datenspeicher unterstützten Typen
  • Eine Sammlung (z. B. ein java.util.List<...>) von Werten eines zentralen Datenspeichertyps
  • Eine Instanz oder Sammlung von Instanzen einer @Entity-Klasse
  • Eine eingebettete Klasse, gespeichert als Attribute der Entität

Datenklassen müssen einen öffentlichen oder geschützten Standardkonstruktor aufweisen und es muss ein Feld speziell für das Speichern des Primärschlüssels der entsprechenden Datenspeicher-Entität bestimmt sein. Sie können zwischen vier verschiedenen Arten von Schlüsselfeldern wählen, die jeweils einen anderen Werttyp und andere Annotationen verwenden. Weitere Informationen finden Sie unter Daten erstellen: Schlüssel. Das einfachste Schlüsselfeld weist als Wert eine lange Ganzzahl auf, die beim erstmaligen Speichern des Objekts im Datenspeicher automatisch von JPA eingetragen wird. Dieser Wert ist über alle Instanzen der Klasse hinweg eindeutig. Bei Schlüsseln in Form von Ganzzahlen vom Typ "long" werden die Annotationen @Idund @GeneratedValue(strategy = GenerationType.IDENTITY) verwendet:

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;

Hier sehen Sie ein Beispiel für eine Datenklasse:

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;
    }
}

Vererbung

JPA unterstützt das Erstellen von Datenklassen, die Vererbung verwenden. Bevor Sie sich damit befassen, wie in App Engine die JPA-Vererbung funktioniert, sollten Sie die DataNucleus-Dokumentation zu diesem Thema lesen und dann an dieser Stelle fortfahren. Fertig? Okay. Die JPA-Vererbung bei App Engine funktioniert so, wie in der DataNucleus-Dokumentation beschrieben. Allerdings gelten einige zusätzliche Einschränkungen. Wir erörtern diese Einschränkungen und geben dann einige konkrete Beispiele.

Mit der Vererbungsstrategie "JOINED" können Sie die Daten für ein einzelnes Datenobjekt auf mehrere "Tabellen" aufteilen. Da der App Engine-Datenspeicher allerdings keine Joins unterstützt, muss bei dieser Vererbungsstrategie für jede Vererbungsebene eines Datenobjekts ein Remoteprozeduraufruf ausgeführt werden. Da dies potenziell sehr ineffizient ist, wird die Vererbungsstrategie "JOINED" für Datenklassen nicht unterstützt.

Zweitens ermöglicht die Vererbungsstrategie "SINGLE_TABLE" das Speichern der Daten für ein Datenobjekt in einer einzelnen "Tabelle", die der persistenten Klasse im Stammverzeichnis Ihrer Vererbungshierarchie zugeordnet ist. Auch wenn diese Strategie keine grundsätzlichen Leistungsschwächen aufweist, wird sie dennoch derzeit nicht unterstützt. Dies wird sich möglicherweise in zukünftigen Versionen ändern.

Jetzt die gute Nachricht: Die Strategien "TABLE_PER_CLASS" und "MAPPED_SUPERCLASS" funktionieren wie in der DataNucleus-Dokumentation beschrieben. Ein Beispiel:

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;
}

In diesem Beispiel haben wir die @MappedSuperclass-Annotation zur Deklaration der Klasse Worker hinzugefügt. Damit wird JPA angewiesen, alle persistenten Felder von Worker in den Datenspeicher-Entitäten seiner Unterklassen zu speichern. Die Datenspeicher-Entität, die durch den Aufruf von persist() mit einer Employee-Instanz erstellt wurde, hat zwei Attribute mit den Namen "department" und "salary". Die Datenspeicher-Entität, die durch den Aufruf von persist() mit einer Intern-Instanz erstellt wurde, hat zwei Attribute mit den Namen "department" und "internshipEndDate". Der Datenspeicher enthält keine Entitäten vom Typ "Worker".

Nun wollen wir die Sache ein wenig interessanter machen. Angenommen, wir wünschen neben Employee und Intern noch eine Spezialisierung von Employee für Arbeitnehmer, die das Unternehmen verlassen haben:

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;
}

In diesem Beispiel haben wir die @Inheritance-Annotation zur Deklaration der Klasse FormerEmployee hinzugefügt, wobei das strategy-Attribut auf InheritanceType.TABLE_PER_CLASS gesetzt wurde. Damit wird JPA angewiesen, alle persistenten Felder von FormerEmployee und der zugehörigen übergeordneten Klassen in Datenspeicherentitäten zu speichern, die FormerEmployee-Instanzen entsprechen. Die Datastore-Entität, die als Ergebnis des Aufrufs von persist() mit einer FormerEmployee-Instanz erstellt wird, hat drei Attribute namens "department", "salary" und "lastDay". Es wird keine Entität vom Typ "Employee" geben, die einem FormerEmployee entspricht. Wenn Sie jedoch persist() mit einem Objekt aufrufen, dessen Laufzeittyp Employee ist, erstellen Sie damit eine Entität vom Typ "Employee".

Die Mischung von Beziehungen und Vererbung funktioniert so lange, wie die deklarierten Typen Ihrer Beziehungsfelder mit den Laufzeittypen der Objekte übereinstimmen, die Sie diesen Feldern zuweisen. Weitere Informationen finden Sie im Abschnitt über polymorphe Beziehungen. Dieser Abschnitt enthält JDO-Beispiele, die Konzepte und Einschränkungen gelten jedoch gleichermaßen für JPA.

Nicht unterstützte Funktionen von JPA 2.0

Die folgenden Funktionen der JPA-Schnittstelle werden nicht von der App Engine-Implementierung unterstützt:

  • m:n-Beziehungen mit Abhängigkeiten
  • "Join"-Abfragen: Bei der Durchführung einer Abfrage für einen übergeordneten Typ können Sie nicht das Feld einer untergeordneten Entität in einem Filter verwenden. Beachten Sie, dass Sie das Beziehungsfeld des übergeordneten Elements mithilfe eines Schlüssels direkt in einer Abfrage testen können.
  • Aggregationsabfragen (group by, having, sum, avg, max, min)
  • Polymorphe Abfragen. Sie können keine Abfrage einer Klasse ausführen, um Instanzen einer abgeleiteten Klasse abzurufen. Jede Klasse wird durch einen eigenen Entitätstyp im Datenspeicher dargestellt.