Utilizzo di JDO 3.0 con App Engine

Java Data Objects (JDO) è un'interfaccia standard per accedere ai database in Java, che fornisce una mappatura tra le classi Java e le tabelle del database. È disponibile un plug-in open source per l'utilizzo di JDO con Datastore e questa pagina fornisce informazioni su come iniziare a utilizzarlo.

Avviso: riteniamo che la maggior parte degli sviluppatori avrà un'esperienza migliore utilizzando l'API Datastore di basso livello o una delle API open source sviluppate appositamente per Datastore, come Objectify. JDO è stato progettato per l'utilizzo con i database relazionali tradizionali e, pertanto, non ha modo di rappresentare esplicitamente alcuni aspetti di Datastore che lo rendono diverso dai database relazionali, come i gruppi di entità e le query sugli antenati. Ciò può portare a problemi sottili che sono difficili da comprendere e risolvere.

L'SDK Java di App Engine include la versione 2.x del plug-in DataNucleus per App Engine. Questo plug-in corrisponde alla versione 3.0 della DataNucleus Access Platform, che ti consente di utilizzare App Engine Datastore tramite JDO 3.0.

Per ulteriori informazioni su JDO, consulta la documentazione di Access Platform 3.0. In particolare, consulta JDO Mapping e API JDO.

Avviso: il plug-in DataNucleus 2.x per App Engine utilizza DataNucleus 3.x. Questo nuovo plug-in non è completamente compatibile con le versioni precedenti (1.x). Se esegui l'upgrade alla nuova versione, assicurati di aggiornare e testare l'applicazione.

Strumenti di compilazione che supportano JDO 2.x e 3.0

Puoi utilizzare Maven per usare la versione 2.x o 3.0 del plug-in DataNucleus per App Engine:

  • Per gli utenti di Maven: puoi migliorare le classi con le seguenti configurazioni nel file 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>

Migrazione alla versione 2.x del plug-in DataNucleus

Questa sezione fornisce istruzioni per eseguire l'upgrade dell'app in modo da utilizzare la versione 2.x del plug-in DataNucleus per App Engine, che corrisponde a DataNucleus Access Platform 3.0 e JDO 3.0. Questo plug-in non è completamente compatibile con le versioni precedenti e potrebbe cambiare. Se esegui l'upgrade, assicurati di aggiornare e testare il codice dell'applicazione.

Nuovi comportamenti predefiniti

La versione 2.x del plug-in DataNucleus di App Engine ha alcune impostazioni predefinite diverse rispetto alla versione precedente:

  • Le chiamate non transazionali a PersistenceManager.makePersistent() e PersistenceManager.deletePersistent() vengono ora eseguite in modo atomico. Queste funzioni venivano precedentemente eseguite nella transazione successiva o al verificarsi di PersistenceManager.close().
  • Ora non è più presente un'eccezione per l'allocazione duplicata di PersistenceManagerFactory (PMF). Se invece la proprietà di persistenza datanucleus.singletonPMFForName è impostata su true, verrà restituito il PMF singleton attualmente allocato per quel nome.
  • Le relazioni non di proprietà sono ora supportate. Consulta Relazioni non di proprietà.

Modifiche ai file di configurazione

Per eseguire l'upgrade dell'applicazione alla versione 2.x del plug-in DataNucleus di App Engine, devi modificare le impostazioni di configurazione in build.xml e jdoconfig.xml.

Attenzione: Dopo aver aggiornato la configurazione, devi testare il codice dell'applicazione per garantire la compatibilità con le versioni precedenti. Se configuri una nuova applicazione e vuoi utilizzare la versione più recente del plug-in, vai a Configurazione di JDO 3.0.

  1. La proprietà PersistenceManagerFactoryClass è cambiata. Modifica questa riga in jdoconfig.xml:

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

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

In build.xml

Il target copyjars deve essere modificato per adattarsi a DataNucleus 2.x:

  1. Il target copyjars è cambiato. Aggiorna questa sezione:
    <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>
    a:
    <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. Il target datanucleusenhance è cambiato. Aggiorna questa sezione:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    a:
    <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>

Configurazione di JDO 3.0

Per utilizzare JDO per accedere al datastore, un'app App Engine ha bisogno di quanto segue:

  • I file JAR per JDO e il plug-in DataNucleus devono trovarsi nella directory war/WEB-INF/lib/ dell'app.
  • Nella directory jdoconfig.xml dell'app deve essere presente un file di configurazione denominato jdoconfig.xml con una configurazione che indichi a JDO di utilizzare il datastore di App Engine.war/WEB-INF/classes/META-INF/
  • Il processo di compilazione del progetto deve eseguire un passaggio di "miglioramento " post-compilazione sulle classi di dati compilate per associarle all'implementazione JDO.

Copia dei file JAR

I file JAR JDO e Datastore sono inclusi nell'SDK Java di App Engine. Puoi trovarli nella directory appengine-java-sdk/lib/opt/user/datanucleus/v2/.

Copia i file JAR nella directory war/WEB-INF/lib/ della tua applicazione.

Assicurati che appengine-api.jar sia presente anche nella directory war/WEB-INF/lib/. Potresti averlo già copiato al momento della creazione del progetto. Il plug-in DataNucleus di App Engine utilizza questo file JAR per accedere al datastore.

Creazione del file jdoconfig.xml

L'interfaccia JDO richiede un file di configurazione denominato jdoconfig.xml nella directory war/WEB-INF/classes/META-INF/ dell'applicazione. Puoi creare questo file direttamente in questa posizione o fare in modo che il processo di compilazione lo copi da una directory di origine.

Crea il file con i seguenti contenuti:

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

Impostazione del criterio di lettura del datastore e della scadenza della chiamata

Come descritto nella pagina Query Datastore, puoi personalizzare il comportamento del Datastore impostando il criterio per la lettura (coerenza forte o eventuale) e la scadenza della chiamata. In JDO, lo fai specificando i valori desiderati nell'elemento <persistence-manager-factory> del file jdoconfig.xml. Tutte le chiamate effettuate con una determinata istanza PersistenceManager utilizzeranno i valori di configurazione in vigore al momento della creazione del gestore da parte di PersistenceManagerFactory. Puoi anche eseguire l'override di queste impostazioni per un singolo oggetto Query.

Per impostare il criterio per la lettura per un PersistenceManagerFactory, includi una proprietà denominata datanucleus.appengine.datastoreReadConsistency. I valori possibili sono EVENTUAL e STRONG: se non specificato, il valore predefinito è STRONG. Tieni presente, tuttavia, che queste impostazioni si applicano solo alle query sugli antenati all'interno di un determinato gruppo di entità. Le query non relative all'antenato sono sempre coerenti in modo definitivo, indipendentemente dal criterio per la lettura prevalente.

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

Puoi impostare scadenze di chiamata del datastore separate per le letture e per le scritture. Per le letture, utilizza la proprietà JDO standard javax.jdo.option.DatastoreReadTimeoutMillis. Per le scritture, utilizza javax.jdo.option.DatastoreWriteTimeoutMillis. Il valore è un periodo di tempo in millisecondi.

<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
<property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

Se vuoi utilizzare le transazioni tra gruppi (XG), aggiungi la seguente proprietà:

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

Puoi avere più elementi <persistence-manager-factory> nello stesso file jdoconfig.xml, utilizzando diversi attributi name, per utilizzare istanze PersistenceManager con configurazioni diverse nella stessa app. Ad esempio, il seguente file jdoconfig.xml stabilisce due insiemi di configurazione, uno denominato "transactions-optional" e l'altro "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>

Consulta Ottenere un'istanza di PersistenceManager di seguito per informazioni su come creare un PersistenceManager con un insieme di configurazioni denominato.

Miglioramento delle classi di dati

JDO utilizza un passaggio di "miglioramento" post-compilazione nel processo di compilazione per associare le classi di dati all'implementazione JDO.

Puoi eseguire il passaggio di miglioramento sulle classi compilate dalla riga di comando con il seguente comando:

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

Il classpath deve contenere il file JARappengine-tools-api.jar della directoryappengine-java-sdk/lib/, nonché tutti i tuoi livelli di dati.

Per ulteriori informazioni sull'ottimizzatore del bytecode di DataNucleus, consulta la documentazione di DataNucleus.

Ottenere un'istanza di PersistenceManager

Un'app interagisce con JDO utilizzando un'istanza della classe PersistenceManager. Puoi ottenere questa istanza creando un'istanza e chiamando un metodo su un'istanza della classe PersistenceManagerFactory. La factory utilizza la configurazione JDO per creare istanze di PersistenceManager.

Poiché l'inizializzazione di un'istanza PersistenceManagerFactory richiede tempo, un'app deve riutilizzare una singola istanza. Un modo semplice per gestire l'istanza PersistenceManagerFactory è creare una classe wrapper singleton con un'istanza statica, come segue:

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

Suggerimento:"transactions-optional" fa riferimento al nome della configurazione impostata nel file jdoconfig.xml. Se la tua app utilizza più set di configurazione, dovrai estendere questo codice per chiamare JDOHelper.getPersistenceManagerFactory() come preferisci. Il codice deve memorizzare nella cache un'istanza singleton di ogni PersistenceManagerFactory.

L'app utilizza l'istanza di fabbrica per creare un'istanza di PersistenceManager per ogni richiesta che accede al datastore.

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

import PMF;

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

Utilizza PersistenceManager per archiviare, aggiornare ed eliminare oggetti dati e per eseguire query sui datastore.

Al termine dell'utilizzo dell'istanza PersistenceManager, devi chiamarne il metodo close(). È un errore utilizzare l'istanza PersistenceManager dopo aver chiamato il relativo metodo close().

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

Funzionalità non supportate di JDO 3.0

Le seguenti funzionalità dell'interfaccia JDO non sono supportate dall'implementazione di App Engine:

  • Relazioni many-to-many di proprietà.
  • Query "join". Non puoi utilizzare un campo di un'entità secondaria in un filtro quando esegui una query sul tipo principale. Tieni presente che puoi testare il campo della relazione del parente direttamente nella query utilizzando una chiave.
  • Raggruppamento JDOQL e altre query aggregate.
  • Query polimorfiche. Non puoi eseguire una query su una classe per ottenere le istanze di una sottoclasse. Ogni classe è rappresentata da un tipo di entità distinto nel datastore.

Disattivazione delle transazioni e porting delle app JDO esistenti

La configurazione JDO che consigliamo di utilizzare imposta una proprietà denominata datanucleus.appengine.autoCreateDatastoreTxns su true. Si tratta di una proprietà specifica di App Engine che indica all'implementazione JDO di associare le transazioni del datastore alle transazioni JDO gestite nel codice dell'applicazione. Se stai creando una nuova app da zero, probabilmente è ciò che ti serve. Tuttavia, se hai già un'applicazione basata su JDO che vuoi eseguire su App Engine, ti consigliamo di utilizzare una configurazione di persistenza alternativa che imposti il valore di questa proprietà su 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>

Per capire perché può essere utile, ricorda che puoi eseguire operazioni solo sugli oggetti che appartengono allo stesso gruppo di entità all'interno di una transazione. Le applicazioni create utilizzando database tradizionali in genere presuppongono la disponibilità di transazioni globali, che consentono di aggiornare qualsiasi insieme di record all'interno di una transazione. Poiché il datastore di App Engine non supporta le transazioni globali, App Engine genera eccezioni se il codice presuppone la disponibilità di transazioni globali. Anziché esaminare la base di codice (potenzialmente di grandi dimensioni) e rimuovere tutto il codice di gestione delle transazioni, puoi semplicemente disattivare le transazioni del datastore. Ciò non risolve le supposizioni fatte dal codice sull'atomicità delle modifiche di più record, ma ti consente di far funzionare l'app in modo da poterti concentrare sul refactoring del codice transazionale in modo incrementale e in base alle necessità, anziché tutto in una volta.