L'API Java Persistence (JPA) è un'interfaccia standard per l'accesso ai database in Java che fornisce un mapping automatico tra classi Java e tabelle di database. È disponibile un plug-in open source per utilizzare 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 i database relazionali tradizionali, pertanto non ha modo di rappresentare esplicitamente alcuni degli aspetti di Datastore che lo rendono diverso dai database relazionali, come i gruppi di entità e le query dei predecessori. Ciò può portare a problemi poco visibili, difficili da comprendere e risolvere.
L'SDK Java di App Engine include la versione 2.x del plug-in DataNucleus per Datastore. Questo plug-in corrisponde alla versione 3.0 di DataNucleus Access Platform, 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 di 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 creazione che supportano JPA 2.x e 3.0
Puoi utilizzare Apache Ant o Maven per usare 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 imposti il progetto.
- Per gli utenti 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 eseguire l'upgrade dell'app per utilizzare 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 del plug-in 2.x non è completamente compatibile con la versione 1.x e potrebbe cambiare senza avviso. 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 presenta 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 è abilitata per impostazione predefinita. Per ottenere il comportamento predefinito precedente, imposta la proprietà di persistenza
datanucleus.cache.level2.type
su none. In alternativa, includi il plug-in datanucleus-cache nel classpath e imposta la proprietà di persistenzadatanucleus.cache.level2.type
su javax.cache in modo da utilizzare Memcache per la memorizzazione nella cache L2. - Il valore predefinito di
IdentifierFactory
del datastore è datanucleus2. Per ottenere il comportamento precedente, imposta la proprietà di persistenzadatanucleus.identifierFactory
su datanucleus1. - Le chiamate non transazionali a
EntityManager.persist()
,EntityManager.merge()
eEntityManager.remove()
vengono ora eseguite a livello atomico. In precedenza, l'esecuzione avveniva al momento della transazione successiva o il giornoEntityManager.close()
. - JPA ha abilitato
retainValues
, il che significa che i valori dei campi caricati vengono conservati negli oggetti dopo un commit. javax.persistence.query.chunkSize
non è più in uso. Usa invecedatanucleus.query.fetchSize
.- Ora non esiste più un'eccezione per l'allocazione EMF duplicata. Se la proprietà di persistenza
datanucleus.singletonEMFForName
è impostata su true, verrà restituito il valore EMF singleton attualmente allocato per quel nome. - Ora sono supportate le relazioni senza proprietario.
- Datastore Identity è ora supportato.
Per un elenco completo delle nuove funzioni, consulta le note sulla versione.
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 la versione più recente del plug-in DataNucleus, vai alla sezione Configurazione di JPA 2.0.
Attenzione: Dopo aver aggiornato la configurazione, devi testare il codice dell'applicazione per verificarne la compatibilità con le versioni precedenti.
Nel file build.xml
Il target copyjars
deve essere modificato per poter supportare 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>
Da
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>
Da
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 ha bisogno di quanto segue:
- I JAR JPA e datastore devono trovarsi nella directory
war/WEB-INF/lib/
dell'app. - Un file di configurazione denominato
persistence.xml
deve trovarsi nella directorywar/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
I 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 JAR nella directory war/WEB-INF/lib/
dell'applicazione.
Assicurati che appengine-api.jar
si trovi anche nella directory war/WEB-INF/lib/
. Potresti averlo già copiato durante la creazione del progetto. Il plug-in DataNucleus di App Engine utilizza questo JAR per accedere al datastore.
Creazione del file 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 il file direttamente in questa posizione oppure lasciare che il processo di compilazione lo copi da una 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 di Datastore e scadenza chiamate
Come descritto nella pagina
Query
Datastore, puoi impostare il criterio per la lettura (elevata coerenza rispetto a
coerenza finale) e la scadenza per la chiamata al datastore per una
EntityManagerFactory
nel file persistence.xml
.
Queste impostazioni vengono inserite 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 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 valore predefinito è STRONG
.
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
Puoi impostare scadenze separate per le chiamate al datastore per le letture e le scritture. Per
le letture, utilizza la proprietà standard JPA
javax.persistence.query.timeout
. Per le operazioni di 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 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>
Consulta la sezione Recupero di un'istanza EntityManager di seguito per informazioni sulla creazione di un'istanza EntityManager
con un set di configurazione denominato.
Puoi eseguire l'override del criterio per la lettura e della scadenza delle chiamate per un singolo oggetto Query
. Per eseguire l'override del criterio per la lettura per un Query
, chiama il relativo metodo setHint()
come segue:
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()
come segue:
q.setHint("javax.persistence.query.timeout", 3000);
Non è possibile eseguire l'override della configurazione per queste opzioni quando recuperi le entità per chiave.
Miglioramento delle classi di dati
L'implementazione DataNucleus di JPA utilizza un passaggio di "miglioramento" di post-compilazione nel processo di compilazione per associare le classi di dati all'implementazione di JPA.
Puoi eseguire il passaggio di miglioramento delle 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 *
è il numero di versione appropriato
di ogni JAR) della directory appengine-java-sdk/lib/tools/
,
nonché tutte le classi di dati.
Per ulteriori informazioni sul miglioramento dei bytecode DataNucleus, consulta la documentazione di DataNucleus.
Recupero di un'istanza EntityManager
Un'app interagisce con JPA utilizzando un'istanza della classe EntityManager
. Per ottenere questa istanza, crei un'istanza e chiami un metodo su un'istanza della classe EntityManagerFactory
. La fabbrica utilizza la configurazione JPA (identificata con il nome "transactions-optional"
) per creare istanze EntityManager
.
Poiché l'inizializzazione di un'istanza EntityManagerFactory
richiede tempo, ti consigliamo di riutilizzare una singola istanza il più possibile. Un modo semplice per eseguire questa operazione è 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 del set di configurazione 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();
Puoi utilizzare EntityManager
per archiviare, aggiornare ed eliminare oggetti di dati ed eseguire query sul datastore.
Quando hai finito con l'istanza EntityManager
, devi chiamare il relativo metodo close()
. Utilizzare l'istanza EntityManager
dopo aver chiamato il relativo metodo close()
è un errore.
try { // ... do stuff with em ... } finally { em.close(); }
Annotazioni per classi e campi
Ogni oggetto salvato da JPA diventa un'entità nel datastore di App Engine. Il tipo di entità deriva 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 caso conservato).
Per dichiarare una classe Java che può 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 dichiarato esplicitamente come permanenti.
Puoi trovare un grafico che descrive in dettaglio il comportamento di persistenza predefinito di JPA sul sito web di DataNucleus. Per dichiarare esplicitamente un campo come permanente, devi fornirgli 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 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, archiviata come proprietà nell'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, ciascuno con annotazioni e tipi di valore 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 con numeri 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 la documentazione di DataNucleus su questo argomento e di tornare qui. Completato? Ok. L'ereditarietà JPA su App Engine funziona come descritto nella documentazione di DataNucleus con alcune limitazioni aggiuntive. Parleremo di queste restrizioni e faremo alcuni esempi concreti.
La strategia di ereditarietà "JOINED" consente di suddividere i dati per un singolo oggetto di 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à. Ciò è 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 di un oggetto dati in un'unica "tabella" associata alla classe permanente alla radice della gerarchia di ereditarietà. Sebbene non esistano inefficienze intrinseche, questa strategia non è attualmente supportata. Potremmo rivederli nelle prossime versioni.
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
. Questo indica a JPA di archiviare tutti i campi permanenti di Worker
nelle entità del datastore delle sue sottoclassi. L'entità datastore creata come risultato della chiamata a 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 "department" e "inernshipEndDate". Nel datastore non sarà presente alcuna entità di tipo "Worker".
Ora possiamo rendere le cose un po' più interessanti. Supponiamo, oltre ad
avere Employee
e Intern
, di volere 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
. Questo indica a JPA di archiviare tutti i campi permanenti di FormerEmployee
e delle sue superclassi in entità di datastore corrispondenti alle istanze FormerEmployee
. L'entità datastore creata come risultato della chiamata a persist()
con un'istanza FormerEmployee
avrà tre proprietà denominate "department", "salary" e "lastDay". Non ci sarà 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 delle relazioni con l'ereditarietà funziona a condizione che i tipi dichiarati dei campi di relazione corrispondano ai tipi di runtime degli oggetti assegnati a questi campi. Per ulteriori informazioni, 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 caratteristiche dell'interfaccia JPA non sono supportate dall'implementazione di App Engine:
- Possedevano relazioni many-to-many.
- Query di tipo "Unisci". Non puoi utilizzare un campo di un'entità figlio in un filtro quando esegui una query sul tipo padre. Tieni presente che puoi testare il campo della relazione dell'elemento padre direttamente in una query utilizzando una chiave.
- Query di aggregazione (raggruppa per, avendo, somma, media, max, min)
- Query polimorfiche. Non puoi eseguire una query su una classe per recuperare le istanze di una sottoclasse. Ogni classe è rappresentata da un tipo di entità separato nel datastore.