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 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. Questo può portare a sottili problemi difficili da comprendere e risolvere.

La versione 1.x del plug-in è inclusa nell'SDK Java di App Engine, che implementa la versione 1.0 di JPA. L'implementazione si basa sulla versione 1.1 della piattaforma DataNucleus Access.

Nota: le istruzioni riportate in questa pagina si applicano alla versione 1 di JPA, che utilizza la versione 1.x del plug-in DataNucleus per App Engine. È disponibile anche la versione 2.x del plug-in DataNucleus, che consente di utilizzare JPA 2.0. Il plug-in 2.x fornisce una serie di nuove API e funzionalità. Tuttavia, l'upgrade non è completamente compatibile con le versioni precedenti 1.x. Se ricostruisci un'applicazione utilizzando JPA 2.0, devi aggiornare e riesaminare il codice. Per ulteriori informazioni sulla nuova versione, consulta Utilizzo di JPA 2.0 con App Engine.

Configurazione di JPA

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

  • 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 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 passaggio di "miglioramento" post-compilazione sulle classi di dati compilate per associarle all'implementazione JPA.

Copia dei JAR in corso...

I file JAR JPA e Datastore sono inclusi nell'SDK Java di App Engine. Puoi nella directory appengine-java-sdk/lib/user/orm/.

Copia i JAR nel file war/WEB-INF/lib/ dell'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 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 fai in modo che il tuo processo di compilazione copi questo file da un della directory di origine.

Crea il file con i seguenti contenuti:

<?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.store.appengine.jpa.DatastorePersistenceProvider</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </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 vanno nell'elemento <persistence-unit>. Tutti effettuate con una determinata istanza EntityManager, configurazione selezionata al momento della creazione del gestore EntityManagerFactory. Puoi anche eseguire l'override di queste opzioni per un singolo Query (descritto di seguito).

Per impostare il criterio di 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 di chiamata del datastore separate per le letture e per le scritture. Per le letture, utilizza la proprietà standard JPA javax.persistence.query.timeout. Per la scrittura, utilizza datanucleus.datastoreWriteTimeout. Il valore è un periodo 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> 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.store.appengine.jpa.DatastorePersistenceProvider</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.store.appengine.jpa.DatastorePersistenceProvider</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" />
        </properties>
    </persistence-unit>
</persistence>

Consulta la sezione Ottenere un'istanza EntityManager di seguito per informazioni su come creare un EntityManager con un insieme di configurazione denominato.

Puoi ignorare il criterio di lettura e la scadenza della chiamata per un singolo oggettoQuery. 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 ignorare la configurazione di queste opzioni quando recupero le entità per 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

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 sull'ottimizzatore del bytecode DataNucleus, consulta la documentazione di DataNucleus.

Ottenere un EntityManager Istanza

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.

Poiché l'inizializzazione di un'istanza EntityManagerFactory richiede tempo, è consigliabile riutilizzare una singola istanza il più possibile. 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" fa riferimento al nome della configurazione impostata nel file persistence.xml. Se la tua app utilizza più set di configurazione, dovrai estendere questo codice per chiamare Persistence.createEntityManagerFactory() come preferisci. Il tuo 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();

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 di classi e campi

Ogni oggetto salvato da JPA diventa un'entità nel datastore di App Engine. Il tipo di entità è dedotto dal nome semplice della classe (senza il nome del 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 permanente per impostazione predefinita o dichiarati esplicitamente come permanenti. Puoi trovare un grafico che descrive il comportamento di persistenza predefinito di JPA sul sito web di DataNucleus. Per dichiarare esplicitamente un campo come persistente, gli assegni 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 un campo può essere uno dei seguenti:

  • 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 una classe @Entity
  • una classe incorporata, memorizzata come proprietà nell'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 dei quali utilizza un con annotazioni e tipi di valore 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 di interi lunghi 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 utilizzano 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.

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, quindi "JOINED" la strategia di ereditarietà non è supportate sulle 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 esistano modelli inefficienze in questa strategia, al momento non è supportata. Potremmo rivedere nelle versioni future.

Ecco le buone notizie: le strategie "TABLE_PER_CLASS" e "MAPPED_SUPERCLASS" funzionano come descritto nella documentazione di DataNucleus. Vediamo un puro 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à del datastore creata come risultato della chiamata a persist() con un'istanza Intern avrà due proprietà denominate "department" e "internshipEndDate". 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 della classe FormerEmployee con l'attributo 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 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 1.0

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

  • Relazioni di tipo "many-to-many" e "non di proprietà". Puoi implementare relazioni non di proprietà utilizzando valori chiave espliciti, anche se il controllo del tipo non è applicato nell'API.
  • 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à distinto nel datastore.