Java Persistence API (JPA) è un'interfaccia standard per l'accesso ai database in Java, che fornisce una mappatura automatica tra le classi Java e le tabelle di 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 avrà un'esperienza migliore utilizzando l'API Datastore di basso livello o una delle API open source sviluppate appositamente per Datastore, come Objectify. JPA è stato progettato per l'utilizzo con database relazionali tradizionali e quindi 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 di antenati. Ciò può portare a problemi sottili 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 della piattaforma di accesso DataNucleus, che consente di utilizzare App Engine Datastore tramite JPA 2.0.
Per saperne di più 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 v3.x. Il plug-in 2.x non è completamente compatibile con le versioni precedenti del plug-in 1.x. Se esegui l'upgrade alla nuova versione, assicurati di aggiornare e testare l'applicazione.
Strumenti di compilazione che supportano JPA 2.x e 3.0
Puoi utilizzare Apache Ant o Maven per utilizzare 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 file JAR e creare il file di configurazione quando configuri il progetto.
- 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-api-jdo</artifactId> <version>3.1.3</version> </dependency> </dependencies> </plugin>
Migrazione alla versione 2.x del plug-in DataNucleus
Questa sezione fornisce istruzioni per l'upgrade dell'app in modo che utilizzi la versione 2.x del plug-in DataNucleus per App Engine, che corrisponde a DataNucleus Access Platform 3.0 e JPA 2.0. La versione 2.x del plug-in non è completamente compatibile con la versione 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 App Engine DataNucleus ha alcuni valori predefiniti diversi rispetto alla versione 1.x precedente:
- Il "persistence provider" JPA ora è
org.datanucleus.api.jpa.PersistenceProviderImpl. - La memorizzazione nella cache di livello 2 è attiva per impostazione predefinita. Per ottenere il comportamento predefinito precedente, imposta la proprietà di persistenza
datanucleus.cache.level2.typesu none. (In alternativa, includi il plug-in datanucleus-cache nel classpath e imposta la proprietà di persistenzadatanucleus.cache.level2.typesu javax.cache per utilizzare Memcache per la memorizzazione nella cache di livello 2. - Datastore
IdentifierFactoryora utilizza per impostazione predefinita datanucleus2. Per ottenere il comportamento precedente, imposta la proprietàdatanucleus.identifierFactorysu datanucleus1. - Le chiamate non transazionali a
EntityManager.persist(),EntityManager.merge()eEntityManager.remove()ora vengono eseguite in modo atomico. (In precedenza, l'esecuzione avveniva alla transazione successiva o il giornoEntityManager.close(). - JPA ha
retainValuesabilitato, il che significa che i valori dei campi caricati vengono conservati negli oggetti dopo un commit. javax.persistence.query.chunkSizenon viene più utilizzato. Utilizzadatanucleus.query.fetchSize.- Ora non esiste più un'eccezione per l'allocazione duplicata di EMF. Se la proprietà di persistenza
datanucleus.singletonEMFForNameè impostata su true, viene restituito l'EMF singleton attualmente allocato per quel nome. - Ora sono supportate le relazioni non di proprietà.
- L'identità Datastore è ora supportata.
Per un elenco completo delle nuove funzionalità, consulta le note di rilascio.
Modifiche ai file di configurazione
Per eseguire l'upgrade dell'app in modo che utilizzi 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 l'ultima versione del plug-in DataNucleus, vai a
Configurazione di JPA 2.0.
Attenzione: Dopo aver aggiornato la configurazione, devi testare il codice dell'applicazione per garantire la compatibilità con le versioni precedenti.
In build.xml
Il target copyjars deve essere modificato per adattarsi a
DataNucleus 2.x:
- 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>
- 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 questa
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 deve avere quanto segue:
- I file JAR JPA e datastore devono trovarsi nella directory
war/WEB-INF/lib/dell'app. - Un file di configurazione denominato
persistence.xmldeve trovarsi nella directorywar/WEB-INF/classes/META-INF/dell'app, con la configurazione che indica a JPA di utilizzare il datastore App Engine. - La 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 file JAR
I file JAR JPA 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/
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 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
direttamente in questa posizione oppure fare in modo che la processo di compilazione lo copi da una
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.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 del datastore e scadenza della chiamata
Come descritto nella pagina
Query
Datastore, puoi impostare la criterio per la lettura (elevata coerenza o coerenza
finale) e la scadenza della chiamata Datastore per un
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 al momento della creazione del gestore da parte di
EntityManagerFactory. Puoi anche eseguire l'override di queste opzioni per un
singolo Query (descritto di seguito).
Per impostare la 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 separate per le chiamate al datastore per le letture e per le scritture. Per le
letture, utilizza la proprietà standard JPA
javax.persistence.query.timeout. Per le scritture, 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 transazioni cross-group (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 set di configurazione, uno denominato "transactions-optional" e un altro denominato "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>
Per informazioni sulla creazione di un EntityManager con un set di configurazione denominato, vedi Ottenere un'istanza di EntityManager di seguito.
Puoi ignorare le criterio per la lettura e la scadenza della chiamata per un singolo oggetto
Query. Per ignorare la criterio per la lettura per un Query,
chiama il relativo metodo setHint() nel seguente modo:
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() nel seguente modo:
q.setHint("javax.persistence.query.timeout", 3000);
Non è possibile ignorare la configurazione di queste opzioni quando recuperi le entità per chiave.
Miglioramento delle classi di dati
L'implementazione di JPA di DataNucleus utilizza un passaggio di "miglioramento" post-compilazione nel processo di compilazione per associare le classi di dati all'implementazione JPA.
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é tutte le classi di dati.
Per saperne di più su DataNucleus bytecode enhancer, consulta la documentazione di DataNucleus.
Recupero di un'istanza EntityManager
Un'app interagisce con JPA utilizzando un'istanza della classe EntityManager. Ottieni questa istanza creando un'istanza e chiamando un metodo su un'istanza della classe EntityManagerFactory. La fabbrica utilizza la
configurazione JPA (identificata dal nome "transactions-optional")
per creare istanze EntityManager.
Poiché l'inizializzazione di un'istanza EntityManagerFactory richiede tempo, è consigliabile riutilizzare una singola istanza il più possibile. 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" si riferisce 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 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 al datastore.
import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import EMF; // ... EntityManager em = EMF.get().createEntityManager();
Utilizzi EntityManager per archiviare, aggiornare ed eliminare oggetti di dati ed eseguire query datastore.
Al termine dell'utilizzo dell'istanza EntityManager, devi chiamare
il relativo metodo close(). È un errore utilizzare l'istanza
EntityManager dopo aver chiamato il relativo metodo close().
try { // ... do stuff with em ... } finally { em.close(); }
Annotazioni di classe e campo
Ogni oggetto salvato da JPA diventa un'entità nel datastore App Engine. Il tipo dell'entità deriva dal nome semplice della classe (senza il nome del pacchetto). Ogni campo persistente della classe rappresenta una proprietà dell'entità, con il nome della proprietà uguale al nome del campo (con la distinzione tra maiuscole e minuscole).
Per dichiarare una classe Java in grado di essere archiviata e recuperata 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 dichiarati esplicitamente come persistenti.
Puoi trovare un grafico che illustra il comportamento di persistenza predefinito di JPA sul
sito web di
DataNucleus. Per dichiarare esplicitamente un campo come persistente, devi assegnargli
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 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 una classe
@Entity - una classe incorporata, memorizzata come proprietà dell'entità
Una classe di dati deve avere un costruttore predefinito pubblico o protetto e un
campo dedicato all'archiviazione della chiave primaria dell'entità
datastore corrispondente. Puoi scegliere tra quattro diversi tipi di campi chiave, ognuno dei quali utilizza un
tipo di valore e annotazioni diversi. Per ulteriori informazioni, consulta Creazione di dati: chiavi. Il campo chiave più semplice è un valore intero lungo
che viene compilato automaticamente da JPA con un valore univoco in tutte le
altre istanze della classe quando l'oggetto viene salvato nel datastore per la
prima volta. Le chiavi intere 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 una classe di dati di esempio:
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 la documentazione di DataNucleus sull'argomento e poi tornare qui. Completato? Ok. L'ereditarietà JPA su App Engine funziona come descritto nella documentazione di DataNucleus con alcune limitazioni aggiuntive. Esamineremo queste limitazioni e poi forniremo alcuni esempi concreti.
La strategia di ereditarietà "JOINED" consente di dividere i dati di un singolo oggetto dati in più "tabelle", ma poiché il datastore App Engine non supporta i join, l'operazione 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 la strategia di ereditarietà "JOINED" non è supportata nelle classi di dati.
In secondo luogo, la strategia di ereditarietà "SINGLE_TABLE" 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 ci siano inefficienze intrinseche in questa strategia, al momento non è supportata. Potremmo rivedere questa funzionalità nelle versioni future.
Ora la buona notizia: le strategie "TABLE_PER_CLASS" e "MAPPED_SUPERCLASS" funzionano come descritto nella documentazione di DataNucleus. Vediamo un 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 alla
dichiarazione della classe Worker. In questo modo, JPA memorizza tutti i campi persistenti di Worker nelle entità datastore delle relative sottoclassi. L'entità datastore creata in seguito alla chiamata di
persist() con un'istanza Employee avrà due
proprietà denominate "department" e "salary". L'entità datastore creata come risultato della chiamata di persist() con un'istanza Intern avrà due proprietà denominate "department" e "internshipEndDate". Non ci saranno entità di tipo "Worker" nel datastore.
Ora rendiamo le cose un po' più interessanti. Supponiamo che, oltre a
Employee e Intern, vogliamo anche una
specializzazione di Employee che descriva i dipendenti che hanno lasciato
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. In questo modo, JPA memorizza tutti i campi persistenti di FormerEmployee e delle relative superclassi in entità datastore corrispondenti alle istanze di FormerEmployee. L'entità datastore creata in seguito alla chiamata di
persist() con un'istanza FormerEmployee avrà
tre proprietà denominate "department", "salary" e "lastDay". Non esisterà mai un'entità di tipo "Employee" che corrisponda a un FormerEmployee, ma se chiami persist() con un oggetto il cui tipo di runtime è Employee, creerai un'entità di tipo "Employee".
La combinazione di relazioni e ereditarietà funziona a condizione che i tipi dichiarati dei campi di relazione corrispondano ai tipi di runtime degli oggetti che stai assegnando a questi campi. Per saperne di più, consulta la sezione Relazioni polimorfiche. 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 esegui una query sul tipo principale. Tieni presente che puoi testare il campo relazione genitore direttamente in una query utilizzando una chiave.
- Query di aggregazione (group by, having, sum, avg, max, min)
- Query polimorfiche. Non puoi eseguire una query di una classe per ottenere istanze di una sottoclasse. Ogni classe è rappresentata da un tipo di entità separato nel datastore.