Utilizzo di JPA con App Engine

L'API Java Persistence (JPA) è un'interfaccia standard per accedere ai database in Java, fornendo una mappatura automatica tra le classi e il database Java tabelle. È disponibile un plug-in open source per utilizzare JPA con Datastore. Questa pagina fornisce informazioni su come iniziare a utilizzarlo.

Avviso: riteniamo che la maggior parte degli sviluppatori l'utilizzo dell'API Datastore di basso livello o di una delle le API open source sviluppate appositamente per Datastore, come Objectify. JPA è stato progettato per essere utilizzato con database relazionali tradizionali e quindi non ha per rappresentare esplicitamente alcuni aspetti di Datastore che lo rendono in modo diverso rispetto ai database relazionali, come i gruppi di entità e le query predecessore. Questo può portare a sottili problemi difficili da comprendere e risolvere.

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

Consulta: il Accedi alla documentazione di Platform 3.0 per ulteriori informazioni su JPA. Nella particolare, consulta JPA Documentazione.

Avviso:versione 2.x di DataNucleus per App Engine utilizza DataNucleus v3.x. Il plug-in 2.x non è completamente compatibile con le versioni precedenti del plug-in 1.x. Se esegui l'upgrade al nuovo aggiorna e testa l'applicazione.

Creazione di strumenti che supportano JPA 2.x e 3.0

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

  • Per gli utenti Ant:l'SDK include un'attività Ant che esegue la passaggio di miglioramento. Devi copiare i JAR e creare il file di configurazione quando per configurare il progetto.
  • Per gli utenti Maven: puoi migliorare i corsi con quanto segue 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-api-jdo</artifactId>
                            <version>3.1.3</version>
                        </dependency>
                    </dependencies>
                </plugin>

Migrazione alla versione 2.x del plug-in DataNucleus

Questa sezione fornisce le istruzioni per eseguire l'upgrade dell'app all'uso della versione 2.x del plug-in DataNucleus per App Engine, che corrisponde a DataNucleus Accedi alla Piattaforma 3.0 e JPA 2.0. La versione del plug-in 2.x non è completamente è compatibile con le versioni precedenti alla versione 1.x e può cambiare senza preavviso. 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 alcuni valori predefiniti diversi rispetto alla versione 1.x precedente:

  • "Fornitore di persistenza" JPA è ora org.datanucleus.api.jpa.PersistenceProviderImpl.
  • La memorizzazione nella cache di livello 2 è abilitata per impostazione predefinita. Per ottenere l'impostazione predefinita precedente comportamento, imposta la proprietà di persistenza Da datanucleus.cache.level2.type a nessuno. (In alternativa, includere il plug-in datanucleus-cache nel classpath e impostare la persistenza datanucleus.cache.level2.type in javax.cache in e usare Memcache per la memorizzazione nella cache L2.
  • Ora il valore predefinito di Datastore IdentifierFactory è datanucleus2. Per ottenere il comportamento precedente, imposta la persistenza proprietà datanucleus.identifierFactory in datanucleus1.
  • Chiamate non transazionali a EntityManager.persist(), EntityManager.merge() e EntityManager.remove() vengono eseguiti a livello atomico. (In precedenza, l'esecuzione avveniva al transazione o a partire da EntityManager.close().
  • JPA ha retainValues abilitato, il che significa che i valori di e vengono conservati negli oggetti dopo un commit.
  • javax.persistence.query.chunkSize non è più in uso. Utilizza le funzionalità di datanucleus.query.fetchSize in alternativa.
  • Non esistono più eccezioni all'allocazione EMF duplicata. Se avere la proprietà di persistenza datanucleus.singletonEMFForName impostata su true, restituirà l'EMF singleton attualmente allocato per quel nome.
  • Ora sono supportate relazioni senza proprietà.
  • Datastore Identity è ora supportato.

Per un elenco completo delle nuove funzionalità, vedi rilascio note.

Modifiche ai file di configurazione

Per eseguire l'upgrade dell'app in modo che utilizzi la versione 2.0 del plug-in DataNucleus per l'app Engine, devi modificare alcune impostazioni di configurazione in build.xml e persistence.xml. Se stai configurando una nuova applicazione e utilizzare l'ultima versione del plug-in DataNucleus, procedi su Configurazione di JPA 2.0.

Attenzione: Dopo aver aggiornato la configurazione, occorre testare il codice dell'applicazione per verificarne la compatibilità con le versioni precedenti.

Nel file build.xml

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

  1. Il target copyjars è stato modificato. 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 è stato modificato. Aggiorna 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>

In persistence.xml

Il target <provider> è stato modificato. Aggiorna sezione:

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

to:

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

Configurazione di JPA 2.0

Per utilizzare JPA per accedere al datastore, un'app App Engine ha bisogno seguenti:

  • I JAR giapponesi e datastore devono essere Directory war/WEB-INF/lib/.
  • Un file di configurazione denominato persistence.xml deve essere nel directory war/WEB-INF/classes/META-INF/ dell'app, con configurazione che indica a JPA di usare il datastore di App Engine.
  • Il processo di compilazione del progetto deve eseguire un "miglioramento" di post-compilazione passaggio sulle classi di dati compilate per associarle all'JPA implementazione.

Copia dei JAR in corso...

I JAR JPA e datastore sono inclusi con l'SDK Java di App Engine. Puoi trovali in appengine-java-sdk/lib/opt/user/datanucleus/v2/ .

Copia i JAR nel file war/WEB-INF/lib/ dell'applicazione .

Assicurati che appengine-api.jar sia anche nel Directory war/WEB-INF/lib/. (Forse lo hai già copiato quando la creazione del progetto). Il plug-in DataNucleus di App Engine utilizza questo JAR per per accedere al datastore.

Creazione del file persistence.xml

L'interfaccia JPA richiede un file di configurazione denominato persistence.xml nel campo Directory war/WEB-INF/classes/META-INF/. Puoi creare questo file in questa posizione oppure chiedi al tuo processo di compilazione di copiare questo file da un nella directory di origine.

Crea il file con il seguente contenuto:

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

Norme di lettura e scadenza delle chiamate Datastore

Come descritto nella Datastore Query, puoi impostare il criterio per la lettura (elevata coerenza oppure finale coerenza) e la scadenza della chiamata al datastore per EntityManagerFactory nel file persistence.xml. Queste impostazioni vengono inserite nell'elemento <persistence-unit>. Tutti effettuate con una determinata istanza EntityManager, configurazione selezionata al momento della creazione del gestore EntityManagerFactory. Puoi anche sostituire queste opzioni per singolo Query (descritto di seguito).

Per impostare il criterio per la lettura, includi una proprietà denominata datanucleus.appengine.datastoreReadConsistency. I suoi valori possibili sono EVENTUAL (per letture con coerenza finale) e STRONG (per letture con elevata coerenza). Se non specificato, il parametro Il valore predefinito è STRONG.

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

Puoi impostare scadenze separate per le chiamate del datastore per letture e scritture. Per utilizza la proprietà standard JPA javax.persistence.query.timeout. Per la scrittura, utilizza datanucleus.datastoreWriteTimeout. Il valore è una quantità di tempo in millisecondi.

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

Se vuoi utilizzare le transazioni tra gruppi (XG), aggiungi quanto segue proprietà:

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

Puoi avere più elementi <persistence-unit> nella stesso file persistence.xml, utilizzando name diversi , per utilizzare EntityManager istanze con diversi configurazioni nella stessa app. Ad esempio, Il file persistence.xml stabilisce due insiemi di configurazione, uno chiamato "transactions-optional" e un altro con nome "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>

Consulta Ottenere un EntityManager istanza di seguito per informazioni sulla creazione di un EntityManager con un set di configurazione denominato.

Puoi ignorare il criterio per la lettura e la scadenza della chiamata per un singolo Query oggetto. Per eseguire l'override del criterio per la lettura per un Query, chiama il metodo setHint() nel modo seguente:

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

Come sopra, i valori possibili sono "EVENTUAL" e "STRONG".

Per eseguire l'override del timeout di lettura, chiama setHint() in questo modo:

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

Non è possibile eseguire l'override della configurazione di queste opzioni quando recupera le entità in base alla chiave.

Miglioramento delle classi di dati

L'implementazione DataNucleus di JPA utilizza un "enhancement" di post-compilazione passaggio del processo di compilazione per associare le classi di dati all'JPA implementazione.

Puoi eseguire il passaggio di miglioramento per le classi compilate dal comando con il seguente comando:

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

classpath deve contenere i JAR datanucleus-core-*.jar datanucleus-jpa-* datanucleus-enhancer-*.jar, asm-*.jar e geronimo-jpa-*.jar (dove * è la versione appropriata numero di ogni JAR) dal appengine-java-sdk/lib/tools/ e tutte le tue classi di dati.

Per ulteriori informazioni sul potenziatore bytecode DataNucleus, vedi il documentazione di DataNucleus.

Recupero di un'istanza EntityManager

Un'app interagisce con JPA utilizzando un'istanza di EntityManager . Puoi ottenere questa istanza creando un'istanza e chiamando un metodo su una dell'istanza della classe EntityManagerFactory. La fabbrica utilizza Configurazione JPA (identificata dal nome "transactions-optional") per creare EntityManager istanze.

Perché un'istanza EntityManagerFactory richiede tempo inizializzare un'istanza, è consigliabile riutilizzare il più possibile una singola istanza. Un il modo più semplice per farlo è creare una classe wrapper singleton con un , come segue:

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

Suggerimento: "transactions-optional" si riferisce alla nome della configurazione impostata nel file persistence.xml. Se le tue utilizza più set di configurazione, devi estendere questo codice per chiamare Persistence.createEntityManagerFactory() a tua scelta. Il tuo codice deve memorizzare nella cache un'istanza singleton di ogni EntityManagerFactory.

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

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

import EMF;

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

Puoi utilizzare EntityManager per archiviare, aggiornare ed eliminare i dati ed eseguire query sul datastore.

Quando hai finito di utilizzare l'istanza EntityManager, devi chiamare il metodo close(). È un errore utilizzare EntityManager dopo la chiamata alla relativa close() .

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

Annotazioni per corsi e campi

Ogni oggetto salvato da JPA diventa un'entità nel datastore di App Engine. La il tipo di entità deriva dal nome semplice della classe (senza il pacchetto ). Ogni campo permanente della classe rappresenta una proprietà dell'entità, con il nome della proprietà uguale a quello del campo (con maiuscole e minuscole) conservati).

Per dichiarare una classe Java che può essere memorizzata e recuperata dal datastore con JPA, assegna alla classe un'annotazione @Entity. Per esempio:

import javax.persistence.Entity;

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

I campi della classe di dati da archiviare nel datastore devono essere di un tipo persistente per impostazione predefinita o dichiarato esplicitamente come permanente. È disponibile un grafico con i dettagli sul comportamento predefinito della persistenza JPA su il Sito web DataNucleus. Per dichiarare esplicitamente un campo come permanente, devi fornire un'annotazione @Basic:

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

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

// ...
    @Basic
    private ShortBlob data;

Il tipo di campo può essere:

  • uno dei tipi di core supportati dal datastore
  • una raccolta (ad esempio java.util.List<...>) di valori di un tipo di datastore principale
  • un'istanza o una raccolta di istanze di un @Entity classe
  • una classe incorporata, archiviata come proprietà sull'entità

Una classe di dati deve avere un costruttore predefinito pubblico o protetto e uno campo dedicato all'archiviazione della chiave primaria del datastore corrispondente dell'oggetto. Puoi scegliere tra quattro diversi tipi di campi chiave, ognuno dei quali utilizza un tipo di valore e annotazioni diversi. (Vedi Creazione in corso Dati: chiavi per ulteriori informazioni. Il campo chiave più semplice è un numero intero lungo valore automaticamente compilato da JPA con un valore univoco in tutte altre istanze della classe quando l'oggetto viene salvato nel datastore per per la prima volta. Le chiavi interi lunghe utilizzano un'annotazione @Id e un Annotazione @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;

Ecco un esempio di classe di dati:

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

Ereditarietà

JPA supporta la creazione di classi di dati che usano l'ereditarietà. Prima di parlare di come funziona l'ereditarietà JPA su App Engine, ti consigliamo di leggere il documentazione di DataNucleus su questo argomento. Completato? Ok. JPA l'ereditarietà su App Engine funziona come descritto nella documentazione di DataNucleus con alcune limitazioni aggiuntive. Parleremo di queste restrizioni e poi fai alcuni esempi concreti.

Lo stato "JOINED" la strategia di ereditarietà consente di suddividere i dati per una singola in più "tabelle", ma poiché il datastore di App Engine non supporta i join, che opera su un oggetto dati con questa strategia di ereditarietà richiede una chiamata di procedura remota per ogni livello di ereditarietà. Questo è potenzialmente molto inefficiente, quindi "JOINED" la strategia di ereditarietà non è supportate sulle classi di dati.

Secondo, la tabella "SINGLE_TABLE" la strategia di ereditarietà consente di archiviare i dati per un oggetto dati in una singola "tabella" associata alla classe persistente alla radice della gerarchia di ereditarietà. Sebbene non esistano modelli inefficienze in questa strategia, al momento non è supportata. Potremmo rivedere nelle versioni future.

Ora la buona notizia: la classe "TABLE_PER_CLASS" e "MAPPED_SUPERCLASS" strategie funzionano come descritto nella documentazione di DataNucleus. Diamo un'occhiata esempio:

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 questo esempio abbiamo aggiunto un'annotazione @MappedSuperclass a la dichiarazione di classe Worker. Questo indica ad JPA di archiviare tutti e campi permanenti di Worker nelle entità del datastore le sottoclassi del deployment. L'entità datastore creata a seguito della chiamata persist() con un'istanza Employee avrà due strutture denominate "reparto" e "stipendio". L'entità datastore creata come risultato della chiamata a persist() con un'istanza Intern avrà due proprietà denominate "reparto" e "inernshipEndDate". Ci sarà non essere entità di tipo "Worker" nel datastore.

Ora rendiamo le cose un po' più interessanti. Supponiamo, oltre a con Employee e Intern, vogliamo anche che specializzazione di Employee che descrive i dipendenti che hanno lasciato l'azienda l'azienda:

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 questo esempio abbiamo aggiunto un'annotazione @Inheritance alla Dichiarazione di classe FormerEmployee con strategy impostato su InheritanceType.TABLE_PER_CLASS. Questo indica a JPA di archivia tutti i campi permanenti di FormerEmployee e i relativi superclassi in entità del datastore corrispondenti a FormerEmployee di Compute Engine. L'entità datastore creata a seguito della chiamata persist() con un'istanza FormerEmployee avrà tre proprietà denominate "repartment", "salary" e "lastDay". Non ci saranno mai Essere un'entità di tipo "Dipendente" che corrisponde a un FormerEmployee, ma se chiami persist() con un il cui tipo di runtime è Employee; verrà creata un'entità di "Dipendente.

La combinazione delle relazioni con l'ereditarietà funziona purché i tipi di che i campi della relazione corrispondano ai tipi di runtime degli oggetti assegnandoli a questi campi. Consulta la sezione Polimorfico Relazioni per ulteriori informazioni. Questa sezione contiene esempi JDO, ma i concetti e le restrizioni sono gli stessi per JPA.

Funzionalità non supportate di JPA 2.0

L'app non supporta le seguenti funzionalità dell'interfaccia JPA Implementazione del motore:

  • Possedere relazioni di tipo many-to-many.
  • "Partecipa" query. Non puoi utilizzare un campo di un'entità secondaria in un filtro quando eseguendo una query sul tipo padre. Tieni presente che puoi testare l'impostazione della relazione direttamente in una query utilizzando una chiave.
  • Query di aggregazione (raggruppa per, con, somma, media, max, min)
  • Query polimorfiche. Non puoi eseguire una query su una classe per ottenere istanze di una sottoclasse. Ogni classe è rappresentata da un tipo di entità separato nel datastore.