JDO 3.0 mit App Engine verwenden

Java Data Objects (JDO) ist eine Standardschnittstelle für den Zugriff auf Datenbanken in Java und bietet eine Zuordnung zwischen Java-Klassen und Datenbanktabellen. Für die Verwendung von JDO 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. JDO 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 subtilen Problemen führen, die schwer zu verstehen und zu beheben sind.

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

Weitere Informationen zu JDO finden Sie in der Dokumentation zu Access Platform 3.0, insbesondere unter JDO Mapping und JDO API.

Warnung: Das DataNucleus-Plug-in 2.x für App Engine verwendet DataNucleus Version 3.x. Dieses neue Plug-in ist nicht vollständig abwärtskompatibel mit dem vorherigen Plug-in (1.x). 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 JDO 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-core</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 JDO 3.0. Dieses Plug-in ist nicht vollständig abwärtskompatibel mit dem 1.x-Plug-in und kann sich ändern. Wenn Sie ein Upgrade ausführen, sollten Sie Ihren Anwendungscode unbedingt aktualisieren und testen.

Neue Standardverhalten

Version 2.x des DataNucleus-Plug-ins für App Engine hat einige andere Standardeinstellungen als die Vorgängerversion:

  • Nicht transaktionale Aufrufe an PersistenceManager.makePersistent() und PersistenceManager.deletePersistent() werden jetzt atomar ausgeführt. (Zuvor erfolgte die Ausführung bei der nächsten Transaktion oder nach PersistenceManager.close().)
  • Bei doppelten PersistenceManagerFactory-Zuordnungen (PMF) wird keine Ausnahme mehr ausgelöst. Wenn Sie das Persistenzattribut datanucleus.singletonPMFForName auf true festgelegt haben, wird die aktuell zugeordnete Singleton-PMF für den jeweiligen Namen zurückgegeben.
  • Beziehungen ohne Abhängigkeiten werden jetzt unterstützt. Siehe Beziehungen ohne Abhängigkeit.

Änderungen an den Konfigurationsdateien

Um ein Upgrade Ihrer Anwendung auf Version 2.x des DataNucleus-Plug-ins für App Engine durchzuführen, müssen Sie die Konfigurationseinstellungen in build.xml und jdoconfig.xml ändern.

Achtung! Nachdem Sie die Konfiguration aktualisiert haben, müssen Sie Ihren Anwendungscode testen, um Abwärtskompatibilität zu gewährleisten. Wenn Sie eine neue Anwendung einrichten und die neueste Version des Plug-ins verwenden möchten, fahren Sie mit JDO 3.0 einrichten fort.

  1. PersistenceManagerFactoryClass-Attribut hat sich geändert. Ändern Sie diese Zeile in jdoconfig.xml:

    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

    nach
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

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>

JDO 3.0 einrichten

App Engine-Anwendungen setzen für den Zugriff auf den Datenspeicher über JDO Folgendes voraus:

  • Die JARs für JDO und das DataNucleus-Plug-in müssen sich im Verzeichnis war/WEB-INF/lib/ der Anwendung befinden.
  • Eine Konfigurationsdatei mit dem Namen jdoconfig.xml muss im Verzeichnis war/WEB-INF/classes/META-INF/ der Anwendung vorhanden sein, und zwar mit einer Konfiguration, die JDO anweist, den App Engine Datastore zu verwenden.
  • Der Build-Prozess des Projekts muss nach der Kompilierung einen "Optimierungsschritt" für die kompilierten Datenklassen ausführen, um sie der JDO-Implementierung zuzuordnen.

JARs kopieren

Die JDO- und Datastore-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 jdoconfig.xml erstellen

Für die JDO-Schnittstelle ist eine Konfigurationsdatei mit dem Namen jdoconfig.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 Build-Prozess aus einem Quellverzeichnis an diesen Speicherort kopieren lassen.

Erstellen Sie die Datei mit folgendem Inhalt:

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

Leserichtlinie und Zeitlimit für den Aufruf des Datenspeichers festlegen

Wie auf der Seite Datenspeicherabfragen beschrieben können Sie das Verhalten des Datenspeichers anpassen, indem Sie die Leserichtlinie (Strong Consistency oder Eventual Consistency) und das Aufrufzeitlimit festlegen. In JDO geben Sie hierfür die gewünschten Werte im Element <persistence-manager-factory> der Datei jdoconfig.xml an. Alle mit einer bestimmten PersistenceManager-Instanz ausgeführten Aufrufe verwenden die Konfigurationswerte, die beim Erstellen des Managers durch die PersistenceManagerFactory-Instanz wirksam waren. Sie können diese Einstellungen auch für ein einzelnes Query-Objekt überschreiben.

Zum Festlegen der Leserichtlinie für PersistenceManagerFactory fügen Sie ein Attribut namens datanucleus.appengine.datastoreReadConsistency hinzu. Mögliche Werte sind EVENTUAL und STRONG: Wenn Sie nichts angeben, ist der Standardwert STRONG. (Diese Einstellungen gelten jedoch nur für Ancestor-Abfragen innerhalb einer bestimmten Entitätengruppe.) Nicht-Ancestor-Abfragen sind immer Eventual-Consistency-Abfragen, ungeachtet der geltenden Leserichtlinie.

<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 JDO-Standardattribut javax.jdo.option.DatastoreReadTimeoutMillis. Verwenden Sie für Schreibvorgänge javax.jdo.option.DatastoreWriteTimeoutMillis. Der Wert ist ein Zeitraum in Millisekunden.

<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
<property name="javax.jdo.option.DatastoreWriteTimeoutMillis" 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-manager-factory> in dieselbe Datei jdoconfig.xml aufnehmen und dabei unterschiedliche name-Attribute verwenden, um PersistenceManager-Instanzen mit verschiedenen Konfigurationen in derselben Anwendung zu nutzen. Die folgende Datei jdoconfig.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"?>
<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>

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

Datenklassen optimieren

JDO bedient sich nach der Kompilierung eines "Optimierungsschritts" im Build-Prozess, um der JDO-Implementierung Datenklassen zuzuordnen.

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

java -cp classpath com.google.appengine.tools.enhancer.Enhance
class-files

Der Klassenpfad (classpath) muss die JAR-Datei appengine-tools-api.jar aus dem Verzeichnis appengine-java-sdk/lib/ sowie alle Datenklassen enthalten.

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

PersistenceManager-Instanz abrufen

Eine Anwendung interagiert mit JDO über eine Instanz der PersistenceManager-Klasse. Sie erhalten diese Instanz, indem Sie eine Methode für eine Instanz der PersistenceManagerFactory-Klasse instanziieren und aufrufen. Die Factory erstellt mithilfe der JPO-Konfiguration PersistenceManager-Instanzen.

Da die Initialisierung einer PersistenceManagerFactory-Instanz einige Zeit in Anspruch nimmt, sollte eine Anwendung eine einzelne Instanz wiederverwenden. Die PersistenceManagerFactory-Instanz lässt sich leicht verwalten, indem Sie eine Singleton-Wrapper-Klasse mit einer statischen Instanz erstellen. Gehen Sie dazu wie folgt vor:

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

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

Die Anwendung verwendet die Factory-Instanz zum Erstellen einer einzigen PersistenceManager-Instanz für jeden Request, der auf den Datenspeicher zugreift.

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import PMF;

// ...
    PersistenceManager pm = PMF.get().getPersistenceManager();

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

Wenn Sie mit der PersistenceManager-Instanz fertig sind, müssen Sie die zugehörige close()-Methode aufrufen. Verwenden Sie die PersistenceManager-Instanz nach dem Aufrufen ihrer close()-Methode, tritt ein Fehler auf.

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

Nicht unterstützte Funktionen von JDO 3.0

Folgende Funktionen der JDO-Schnittstelle werden nicht von der App Engine-Implementierung unterstützt:

  • N-zu-N-Beziehungen mit Abhängigkeiten.
  • "Join"-Abfragen: Wenn Sie eine Abfrage für einen übergeordneten Entitätstyp ausführen, können Sie kein Feld einer untergeordneten Entität in einem Filter verwenden. Sie können jedoch das Beziehungsfeld der übergeordneten Entität mithilfe eines Schlüssels direkt in einer Abfrage testen.
  • JDOQL-Gruppierung und andere Aggregatabfragen
  • 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.

Transaktionen deaktivieren und vorhandene JDO-Anwendungen portieren

In der von uns empfohlenen JDO-Konfiguration wird ein Attribut namens datanucleus.appengine.autoCreateDatastoreTxns auf true festgelegt. Dies ist eine App Engine-spezifische Property, die in der JDO-Implementierung eine Zuordnung zwischen Datenspeichertransaktionen und den im Anwendungscode verwalteten JDO-Transaktionen bewirkt. Wenn Sie eine neue Anwendung von Grund auf erstellen, ist das vermutlich in Ihrem Sinne. Wenn Sie jedoch bereits über eine JDO-basierte Anwendung verfügen, die Sie auf App Engine ausführen möchten, ist es eventuell besser, eine alternative Persistenzkonfiguration zu verwenden, die den Wert dieses Attributs auf false setzt:

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

Die Nützlichkeit dieser Vorgehensweise erklärt sich aus der Tatsache, dass Sie innerhalb einer Transaktion nur für Objekte, die zur selben Entitätengruppe gehören, Vorgänge ausführen können. Anwendungen, die mit herkömmlichen Datenbanken erstellt wurden, gehen in der Regel von der Verfügbarkeit globaler Transaktionen aus, die die Aktualisierung beliebiger Gruppen von Datensätzen innerhalb einer Transaktion ermöglichen. Da der App Engine-Datenspeicher keine globalen Transaktionen unterstützt, löst App Engine Ausnahmen aus, wenn Ihr Code von der Verfügbarkeit globaler Transaktionen ausgeht. Anstatt Ihre (möglicherweise große) Codebasis durchzugehen und sämtlichen Transaktionsverwaltungscode zu entfernen, können Sie einfach Datenspeichertransaktionen deaktivieren. Dies ändert nichts an den Annahmen, die Ihr Code in Bezug auf die Atomarität von Änderungen an mehreren Datensätzen trifft, es ermöglicht Ihnen jedoch, Ihre Anwendung funktionsfähig zu machen. So können Sie sich anschließend darauf konzentrieren, Transaktionscode nach Bedarf inkrementell zu refaktorieren, anstatt alle Vorgänge auf einmal durchzuführen.