Utilizzo di JPA con App Engine

Java Persistence API (JPA) è un'interfaccia standard per accedere ai database in Java, che fornisce una mappatura automatica tra le classi Java e le tabelle del database. È disponibile un plug-in open source per l'utilizzo di JPA con Datastore e 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 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 Datastore. Questo plug-in corrisponde alla versione 3.0 del Piattaforma di accesso DataNucleus, che consente di utilizzare App Engine Datastore tramite JPA 2.0.

Per ulteriori informazioni su JPA, consulta la documentazione di Access Platform 3.0. In particolare, consulta la documentazione JPA.

Avviso: la versione 2.x del plug-in DataNucleus per App Engine utilizza DataNucleus 3.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 di Ant: l'SDK include un'attività Ant che esegue il 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 1.x e potrebbe 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:

  • Il "provider di persistenza" JPA ora è org.datanucleus.api.jpa.PersistenceProviderImpl.
  • La memorizzazione nella cache di Livello 2 è attiva per impostazione predefinita. Per ottenere l'impostazione predefinita precedente del comportamento, imposta la proprietà di persistenza Da datanucleus.cache.level2.type a nessuno. (In alternativa, includi il plug-in datanucleus-cache nel classpath e imposta la persistenza datanucleus.cache.level2.type in javax.cache in e usare Memcache per la memorizzazione nella cache L2.
  • Per il datastore IdentifierFactory ora è predefinito 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.
  • Ora non è più presente un'eccezione per l'allocazione duplicata degli EMF. 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 da utilizzare la versione 2.0 del plug-in DataNucleus per App Engine, devi modificare alcune impostazioni di configurazione in build.xml e persistence.xml. Se stai configurando una nuova applicazione e vuoi utilizzare la versione più recente del plug-in DataNucleus, vai a 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 supportare 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>

In persistence.xml

Il target <provider> è cambiato. 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 di quanto segue:

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

Copia dei JAR in corso...

I JAR JPA e datastore sono inclusi con l'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 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 file JAR per accedere al datastore.

Creazione del file persistence.xml

L'interfaccia JPA richiede un file di configurazione denominato persistence.xml nella directory war/WEB-INF/classes/META-INF/ dell'applicazione. Puoi creare questo file in questa posizione oppure fai in modo che il tuo processo di compilazione copi questo file da un della 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>

Criterio di lettura del datastore e scadenza chiamata

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 vanno nell'elemento <persistence-unit>. Tutte le chiamate effettuate con una determinata istanza EntityManager utilizzano la configurazione selezionata quando il gestore è stato creato da 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 valori possibili sono EVENTUAL (per le letture con coerenza finale) e STRONG (per le letture con elevata coerenza). Se non specificato, il valore predefinito è STRONG.

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

Puoi impostare scadenze di chiamata del datastore separate per le letture e per le scritture. Per utilizza la proprietà standard JPA javax.persistence.query.timeout. Per le scritture, 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 la seguente proprietà:

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

Puoi avere più elementi <persistence-unit> nello stesso file persistence.xml, utilizzando attributi name diversi, per utilizzare istanze EntityManager con configurazioni diverse nella stessa app. Ad esempio, il seguente file persistence.xml stabilisce due insiemi di configurazione, uno denominato "transactions-optional" e l'altro "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 ignorare il timeout di lettura, chiama setHint() come segue:

        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 sulle classi compilate dalla riga di comando con il seguente comando:

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

Il classpath deve contenere i file JAR datanucleus-core-*.jar, datanucleus-jpa-*, datanucleus-enhancer-*.jar, asm-*.jar e geronimo-jpa-*.jar (dove * è il numero di versione appropriato di ogni JAR) dalla directory appengine-java-sdk/lib/tools/, nonché tutti i tuoi classi di dati.

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

Ottenere un'istanza EntityManager

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

Perché un'istanza EntityManagerFactory richiede tempo inizializzare un'istanza, è consigliabile riutilizzare il più possibile una singola istanza. Un modo semplice per farlo è creare una classe wrapper singleton con un'istanza statica, 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" fa riferimento al 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 codice deve memorizzare nella cache un'istanza singleton di ogni EntityManagerFactory.

L'app utilizza l'istanza di fabbrica per creare un'istanza EntityManager per ogni richiesta che accede all'archivio dati.

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

import EMF;

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

Utilizzi EntityManager per archiviare, aggiornare ed eliminare gli oggetti dati ed eseguire query sul datastore.

Al termine dell'utilizzo dell'istanza EntityManager, devi chiamarne il metodo close(). È un errore utilizzare l'istanza EntityManager dopo aver chiamato il relativo metodo 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 al nome del campo (con la maiuscola conservata).

Per dichiarare una classe Java come archiviabile e recuperabile dal datastore con JPA, assegna alla classe un'annotazione @Entity. Ad 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 persistente, devi attribuirgli 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 principali 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 un campo dedicato allo stoccaggio della chiave primaria dell'entità del datastore corrispondente. Puoi scegliere tra quattro diversi tipi di campi chiave, ognuno con 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. L'eredità JPA su App Engine funziona come descritto nella documentazione di DataNucleus con alcune limitazioni aggiuntive. Parleremo di queste restrizioni e poi fai alcuni esempi concreti.

La strategia di ereditarietà "JOINED" consente di suddividere i dati di un singolo oggetto di dati in più "tabelle", ma poiché il datastore di App Engine non supporta le unioni, l'utilizzo di un oggetto di dati con questa strategia di ereditarietà richiede una chiamata di procedura remota per ogni livello di ereditarietà. Questo è potenzialmente molto inefficiente, pertanto la strategia di ereditarietà "JOINED" non è supportata nelle classi di dati.

Secondo, la tabella "SINGLE_TABLE" di ereditarietà per l'archiviazione dei dati per un oggetto dati in una singola "tabella" associata alla classe persistente alla radice della gerarchia di ereditarietà. Sebbene non siano presenti inefficienze intrinseche 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 a JPA di archiviare tutti i campi permanenti di Worker nelle entità del datastore delle sue sottoclassi. L'entità del datastore creata come risultato della chiamata persist() con un'istanza Employee avrà due proprietà denominate "department" e "salary". L'entità datastore creata come risultato della chiamata a persist() con un'istanza Intern avrà due proprietà denominate "reparto" e "inernshipEndDate". Nel datastore non ci saranno entità di tipo "Lavoratore".

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 memorizzare tutti i campi permanenti di FormerEmployee e delle sue superclassi nelle entità del datastore corrispondenti alle istanze di FormerEmployee. L'entità datastore creata a seguito della chiamata persist() con un'istanza FormerEmployee avrà tre proprietà denominate "repartment", "salary" e "lastDay". Non esisterà mai un'entità di tipo "Dipendente" che corrisponda a un FormerEmployee, ma se chiami persist() con un oggetto il cui tipo di runtime è Employee, creerai un'entità di tipo "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 di JDO, ma i concetti e le limitazioni sono gli stessi per JPA.

Funzionalità non supportate di JPA 2.0

Le seguenti funzionalità dell'interfaccia JPA 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 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 (group by, having, sum, avg, max, min)
  • 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à separato nel datastore.