Utilizzo di JDO 2.3 con App Engine

Java Data Objects (JDO) è un'interfaccia standard per l'accesso ai database in Java che fornisce una mappatura tra classi Java e tabelle di database. È disponibile un plug-in open source per utilizzare 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, pertanto non ha modo di rappresentare esplicitamente alcuni degli aspetti di Datastore che lo rendono diverso dai database relazionali, come i gruppi di entità e le query dei predecessori. Ciò può portare a problemi poco visibili, difficili da comprendere e risolvere.

L'SDK Java di App Engine include un'implementazione di JDO 2.3 per App Engine Datastore. L'implementazione è basata sulla versione 1.0 della DataNucleus Access Platform, l'implementazione di riferimento open source per JDO 2.3.

Nota: le istruzioni in questa pagina si applicano alla versione JDO 2.3, che utilizza la versione 1.0 del plug-in DataNucleus per App Engine. App Engine ora offre il plug-in DataNucleus 2.x che consente di eseguire JDO 3.0. Il nuovo plug-in supporta le relazioni senza proprietà e fornisce una serie di nuove API e funzionalità. Questo upgrade non è completamente compatibile con le versioni precedenti. Se ricrei un'applicazione utilizzando JDO 3.0, devi aggiornare e testare nuovamente il codice. Per ulteriori informazioni sulla nuova versione, consulta JDO 3.0. Per maggiori informazioni sull'upgrade, consulta Utilizzo di JDO 3.0 con App Engine.

Configurazione di JDO 2.3

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

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

Copia dei JAR

I JDO e i JAR del datastore sono inclusi nell'SDK Java di App Engine. Puoi trovarli nella directory appengine-java-sdk/lib/user/orm/.

Copia i JAR nella directory war/WEB-INF/lib/ dell'applicazione.

Assicurati che appengine-api.jar si trovi anche nella directory war/WEB-INF/lib/. Potresti averlo già copiato durante la creazione del progetto. Il plug-in DataNucleus di App Engine utilizza questo 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 oppure fare in modo che il processo di compilazione copi il file da una directory di origine.

Crea il file con il seguente contenuto:

<?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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <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>
</jdoconfig>

Impostazione del criterio di lettura del datastore e della scadenza delle chiamate

Come descritto nella pagina Query Datastore, puoi personalizzare il comportamento di Datastore impostando il criterio per la lettura (coerenza elevata o finale) e la scadenza della chiamata. In JDO, devi specificare 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 validi 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 elemento PersistenceManagerFactory, includi una proprietà denominata datanucleus.appengine.datastoreReadConsistency. I valori possibili sono EVENTUAL e STRONG: se non specificati, il valore predefinito è STRONG. Tieni presente che queste impostazioni si applicano solo alle query dei predecessori all'interno di un determinato gruppo di entità. Le query non predecessori e tra gruppi sono sempre coerenti alla fine, indipendentemente dal criterio per la lettura prevalente.

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

Puoi impostare scadenze separate per le chiamate al datastore per le letture e le scritture. Per le letture, utilizza la proprietà standard JDO javax.jdo.option.DatastoreReadTimeoutMillis. Per le operazioni di scrittura, utilizza javax.jdo.option.DatastoreWriteTimeoutMillis. Il valore è una quantità 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 cross-group (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 attributi name diversi, per utilizzare istanze PersistenceManager con configurazioni diverse nella stessa app. Ad esempio, il seguente file jdoconfig.xml stabilisce due set di configurazione, uno denominato "transactions-optional" e un altro denominato"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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <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" />
    </persistence-manager-factory>
</jdoconfig>

Consulta la sezione Recupero di un'istanza PersistenceManager di seguito per informazioni sulla creazione di un'istanza PersistenceManager con un set di configurazione denominato.

Miglioramento delle classi di dati

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

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

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

classpath deve contenere il JAR appengine-tools-api.jar della directory appengine-java-sdk/lib/, nonché tutte le classi di dati.

Per ulteriori informazioni sul miglioramento dei bytecode DataNucleus, consulta la documentazione di DataNucleus.

Ottenere un'istanza 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 fabbrica utilizza la configurazione JDO per creare istanze PersistenceManager.

Poiché l'inizializzazione di un'istanza PersistenceManagerFactory richiede tempo, un'app deve riutilizzare una singola istanza. Per forzare l'applicazione, viene generata un'eccezione se l'app crea più di un PersistenceManagerFactory (con lo stesso nome di configurazione). 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" si riferisce al nome del set di configurazione 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 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();

Puoi utilizzare PersistenceManager per archiviare, aggiornare ed eliminare oggetti di dati ed eseguire query sul datastore.

Quando hai finito con l'istanza PersistenceManager, devi chiamare il relativo metodo close(). Utilizzare l'istanza PersistenceManager dopo aver chiamato il relativo metodo close() è un errore.

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

Funzionalità non supportate di JDO 2.3

Le seguenti caratteristiche dell'interfaccia JDO non sono supportate dall'implementazione App Engine:

  • Relazioni senza proprietà. Puoi implementare relazioni senza proprietà utilizzando coppie chiave-valore esplicite. La sintassi di JDO per le relazioni senza proprietà potrebbe essere supportata in una release futura.
  • Possedevano relazioni many-to-many.
  • Query di tipo "Unisci". Non puoi utilizzare un campo di un'entità figlio in un filtro quando esegui una query sul tipo padre. Tieni presente che puoi testare il campo della relazione dell'elemento padre direttamente nella query utilizzando una chiave.
  • Raggruppamento JDOQL e altre query aggregate.
  • Query polimorfiche. Non puoi eseguire una query su una classe per recuperare le istanze di una sottoclasse. Ogni classe è rappresentata da un tipo di entità separato nel datastore.
  • IdentityType.DATASTORE per l'annotazione @PersistenceCapable. È supportato solo IdentityType.APPLICATION.
  • Attualmente esiste un bug che impedisce le relazioni one-to-many di proprietà in cui la risorsa padre e quella secondaria appartengono alla stessa classe, il che rende difficile modellare le strutture ad albero. Questo problema verrà risolto in una release futura. Puoi aggirare il problema archiviando valori di chiave espliciti per l'elemento padre o secondario.

Disabilitazione delle transazioni e portabilità di app JDO esistenti

La configurazione JDO che consigliamo di utilizzare imposta una proprietà denominata datanucleus.appengine.autoCreateDatastoreTxns su true. Questa è 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 questa è la soluzione che cerchi. Tuttavia, se hai già un'applicazione basata su JDO che vuoi eseguire su App Engine, puoi 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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <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é potrebbe essere utile, ricorda che puoi operare solo su oggetti che appartengono allo stesso gruppo di entità all'interno di una transazione. Le applicazioni create utilizzando database tradizionali in genere presuppongono la disponibilità delle transazioni globali, il che consente di aggiornare qualsiasi set 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é passare attraverso il tuo codebase (potenzialmente grande) e rimuovere tutto il codice di gestione delle transazioni, puoi semplicemente disabilitare le transazioni del datastore. Questo non risolve le ipotesi che il tuo codice genera sull'atomicità delle modifiche multi-record, ma ti consente di far funzionare la tua app in modo che tu possa concentrarti sul refactoring del codice transazionale in modo incrementale e secondo necessità, anziché tutto contemporaneamente.