Puoi modellare le relazioni tra oggetti permanenti utilizzando i campi tipi di oggetti. Una relazione tra oggetti persistenti può essere descritta come di proprietà, se uno degli oggetti non può esistere senza l'altro, o senza proprietario, se entrambi gli oggetti possono esistere indipendentemente dalla loro relazione tra loro. L'implementazione di App Engine dell'interfaccia JDO può modellare relazioni one-to-one e one-to-many sia di proprietà che non possedute, sia unidirezionali che bidirezionali.
Le relazioni senza proprietà non sono supportate nella versione 1.0 di DataNucleus plug-in per App Engine, ma puoi gestire queste relazioni autonomamente per l'archiviazione diretta delle chiavi del datastore nei campi. App Engine crea automaticamente entità correlate in gruppi di entità per supportare l'aggiornamento di oggetti correlati, ma è responsabilità dell'app sapere quando utilizzare le transazioni del datastore.
La versione 2.x del plug-in DataNucleus per App Engine supporta i file senza proprietà relazioni con una sintassi naturale. La Nella sezione Relazioni senza proprietario viene illustrato come per creare relazioni senza proprietario in ogni versione del plug-in. Per eseguire l'upgrade alla versione 2.x del plug-in DataNucleus per App Engine, consulta Eseguire la migrazione alla versione 2.x del plug-in DataNucleus per App Engine.
Relazioni one-to-one di proprietà
Puoi creare una relazione di proprietà one-to-one unidirezionale tra due oggetti permanenti utilizzando un campo il cui tipo è la classe della classe correlata.
L'esempio seguente definisce una classe di dati ContactInfo e una classe di dati Employee, con una relazione uno a uno da Employee a ContactInfo.
ContactInfo.java
import com.google.appengine.api.datastore.Key; // ... imports ... @PersistenceCapable public class ContactInfo { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String streetAddress; // ... }
Employee.java
import ContactInfo; // ... imports ... @PersistenceCapable public class Employee { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private ContactInfo contactInfo; ContactInfo getContactInfo() { return contactInfo; } void setContactInfo(ContactInfo contactInfo) { this.contactInfo = contactInfo; } // ... }
Gli oggetti permanenti sono rappresentati come due entità distinte nel datastore, con due tipi diversi. La relazione viene rappresentata utilizzando relazione di gruppo di entità: la chiave dell'asset secondario utilizza la chiave dell'elemento padre come entità principale del gruppo. Quando l'app accede all'oggetto secondario utilizzando il campo dell'oggetto principale, l'implementazione JDO esegue una query sul gruppo di entità principale per ottenere l'oggetto secondario.
La classe secondaria deve avere un campo chiave il cui tipo può contenere le informazioni sulla chiave principale: una chiave o un valore della chiave codificato come stringa. Consulta Creazione di dati: chiavi per informazioni sui tipi di campi chiave.
Puoi creare una relazione uno a uno bidirezionale utilizzando i campi di entrambe le classi, con un'annotazione sul campo della classe secondaria per dichiarare che i campi rappresentano una relazione bidirezionale. Il campo della classe secondaria
deve avere un'annotazione @Persistent
con l'argomento
mappedBy = "..."
, dove il valore è il nome del campo nella
principale. Se il campo di un oggetto è compilato, il codice corrispondente
sull'altro oggetto viene compilato automaticamente.
ContactInfo.java
import Employee; // ... @Persistent(mappedBy = "contactInfo") private Employee employee;
Gli oggetti figlio vengono caricati dal datastore quando si accede al datastore
per la prima volta. Se non accedi all'oggetto figlio su un oggetto padre, l'entità
dell'oggetto secondario non viene mai caricato. Se vuoi caricare l'elemento secondario, puoi "toccarlo" prima di chiudere PersistenceManager (ad es. chiamando getContactInfo()
nell'esempio precedente) o aggiungere esplicitamente il campo secondario al gruppo di recupero predefinito in modo che venga recuperato e caricato con l'elemento principale:
Employee.java
import ContactInfo; // ... @Persistent(defaultFetchGroup = "true") private ContactInfo contactInfo;
Relazioni one-to-many di proprietà
Per creare una relazione one-to-many tra oggetti di una classe e diversi oggetti di un'altra, utilizza una raccolta della classe correlata:
Employee.java
import java.util.List; // ... @Persistent private List<ContactInfo> contactInfoSets;
Una relazione bidirezionale one-to-many è simile a una relazione one-to-one, con una
sulla classe padre utilizzando l'annotazione
@Persistent(mappedBy = "...")
, dove il valore è il nome dell'oggetto
campo sulla classe secondaria:
Employee.java
import java.util.List; // ... @Persistent(mappedBy = "employee") private List<ContactInfo> contactInfoSets;
ContactInfo.java
import Employee; // ... @Persistent private Employee employee;
I tipi di raccolta elencati in Definizione Classi di dati: le raccolte sono supportate per le relazioni one-to-many. Tuttavia, gli array non sono supportati per le relazioni one-to-many.
App Engine non supporta le query di join: non puoi eseguire query su un'entità padre utilizzando un attributo di un'entità figlio. Puoi eseguire query su una proprietà di una classe incorporata perché le classi incorporate memorizzano le proprietà nell'entità principale. Consulta Definire le classi di dati: classi incorporate.
Come le raccolte ordinate mantengono l'ordine
Le raccolte ordinate, come List<...>
, mantengono l'ordine
degli oggetti quando viene salvato l'oggetto principale. JDO richiede che i database mantengano questo ordine archiviando la posizione di ogni oggetto come proprietà dell'oggetto.
App Engine lo archivia come una proprietà dell'entità corrispondente, utilizzando un
nome della proprietà uguale al nome del campo dell'elemento principale seguito da
_INTEGER_IDX
. Le proprietà di posizione non sono efficienti. Se
viene aggiunto, rimosso o spostato nella raccolta, da tutte le entità successive
al luogo modificato nella raccolta devono essere aggiornati. Questa operazione può essere lenta e soggetta a errori se non viene eseguita in una transazione.
Se non è necessario preservare un ordine arbitrario in una raccolta, ma per utilizzare un tipo di raccolta ordinato, puoi specificare un ordine basato su delle proprietà degli elementi utilizzando un'annotazione, un'estensione del JDO DataNucleus:
import java.util.List; import javax.jdo.annotations.Extension; import javax.jdo.annotations.Order; import javax.jdo.annotations.Persistent; // ... @Persistent @Order(extensions = @Extension(vendorName="datanucleus",key="list-ordering", value="state asc, city asc")) private List<ContactInfo> contactInfoSets = new ArrayList<ContactInfo>();
L'annotazione @Order
(utilizzando list-ordering
specifica l'ordine desiderato degli elementi della raccolta come
Clausola di ordinamento JDOQL. L'ordinamento utilizza i valori delle proprietà degli elementi.
Come per le query, tutti gli elementi di una raccolta devono avere valori per le proprietà utilizzate nella clausola di ordinamento.
L'accesso a una raccolta esegue una query. Se la clausola di ordinamento di un campo utilizza più di un ordinamento, la query richiede un indice Datastore; vedi il Pagina Indici di Datastore per ulteriori informazioni.
Per maggiore efficienza, utilizza sempre una clausola di ordinamento esplicita per one-to-many relazioni dei tipi di raccolta ordinati, se possibile.
Relazioni non di proprietà
Oltre alle relazioni di proprietà, l'API JDO fornisce anche un'utilità per gestire le relazioni non di proprietà. Questa funzionalità funziona in modo diverso a seconda della versione del plug-in DataNucleus per App Engine in uso:
- La versione 1 del plug-in DataNucleus non implementa le relazioni non di proprietà utilizzando una sintassi naturale, ma puoi comunque gestire queste relazioni utilizzando i valori
Key
al posto delle istanze (o delle raccolte di istanze) degli oggetti del tuo modello. L'archiviazione degli oggetti chiave è come la modellazione di un modello "chiave esterna" tra due oggetti. Il datastore non garantisce integrità referenziale con questi riferimenti chiave, ma l'uso della chiave rende molto facile modellare (e poi recuperare) qualsiasi relazione tra due oggetti.
Tuttavia, se scegli questa opzione, devi assicurarti che le chiavi siano del del tipo appropriato. JDO e il compilatore non controllano i tipiKey
per te. - La versione 2.x del plug-in DataNucleus implementa le relazioni non di proprietà utilizzando una sintassi naturale.
Suggerimento: in alcuni casi, potresti ritenere necessario creare modelli una relazione di proprietà, come se fosse senza proprietario. Questo perché tutti gli oggetti coinvolti in una relazione di proprietà vengono inseriti automaticamente nello stesso gruppo di entità e un gruppo di entità può supportare solo da una a dieci scritture al secondo. Ad esempio, se un oggetto principale riceve 0,75 scritture al secondo e un oggetto secondario ne riceve altre 0,75, potrebbe essere opportuno modellare questa relazione come non di proprietà in modo che sia l'oggetto principale che quello secondario si trovino in gruppi di entità indipendenti.
Relazioni one-to-one senza proprietà
Supponiamo che tu voglia modellare una persona e un cibo, dove una persona può avere un solo cibo preferito, ma un cibo preferito non appartiene alla persona perché può essere il cibo preferito di un numero qualsiasi di persone. Questa sezione mostra come fare quello.
In JDO 2.3
In questo esempio, forniamo a Person
un membro di tipo
Key
, dove Key
è l'identificatore univoco di un
Food
oggetto. Se un'istanza di Person
e l'istanza di Food
a cui si fa riferimento
Person.favoriteFood
non si trovano nello stesso gruppo di entità, tu
non può aggiornare la persona e il suo cibo preferito in un unico posto
transazione, a meno che la configurazione JDO non sia impostata su
attiva
transazioni cross-group (XG).
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Key favoriteFood; // ... }
Food.java
import Person; // ... imports ... @PersistenceCapable public class Food { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; // ... }
In JDO 3.0
In questo esempio, invece di fornire a Person
una chiave che rappresenti
il loro cibo preferito, creiamo un membro privato di tipo Food
:
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent @Unowned private Food favoriteFood; // ... }
Food.java
import Person; // ... imports ... @PersistenceCapable public class Food { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; // ... }
Relazioni one-to-many senza proprietà
Ora supponiamo di voler lasciare che una persona abbia più cibi preferiti. Ancora una volta, un piatto preferito non appartiene alla persona perché può essere il piatto preferito di un numero qualsiasi di persone.
In JDO 2.3
In questo esempio, anziché assegnare a Persona un membro di tipo
Set<Food>
per rappresentare i cibi preferiti della persona,
assegnamo a Persona un membro di tipo Set<Key>
, dove l'insieme
contiene gli identificatori univoci degli oggetti Food
. Tieni presente che, se
un'istanza di Person
e un'istanza di Food
contenuto in Person.favoriteFoods
non si trovano nello stesso gruppo di entità,
devi impostare la configurazione JDO su
attivare
le transazioni tra gruppi (XG) se vuoi aggiornarle nella stessa
transazione.
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Set<Key> favoriteFoods; // ... }
In JDO 3.0
In questo esempio, assegniamo a Persona un membro di tipo Set<Food>
in cui il set rappresenta i cibi preferiti dell'utente.
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Set<Food> favoriteFoods; // ... }
Relazioni many-to-many
Possiamo modellare una relazione many-to-many mantenendo raccolte di chiavi
da entrambi i lati della relazione. Modifichiamo l'esempio per
Food
tiene traccia delle persone che lo considerano preferita:
Person.java
import java.util.Set; import com.google.appengine.api.datastore.Key; // ... @Persistent private Set<Key> favoriteFoods;
Food.java
import java.util.Set; import com.google.appengine.api.datastore.Key; // ... @Persistent private Set<Key> foodFans;
In questo esempio, Person
gestisce un insieme di valori Key
che identificano in modo univoco gli oggetti Food
che sono preferiti e Food
gestisce un insieme di valori Key
che identificano in modo univoco gli oggetti Person
che lo considerano un preferito.
Quando modelli una relazione molti a molti utilizzando i valori Key
, tieni presente che è responsabilità dell'app gestire entrambi i lati della relazione:
Album.java
// ... public void addFavoriteFood(Food food) { favoriteFoods.add(food.getKey()); food.getFoodFans().add(getKey()); } public void removeFavoriteFood(Food food) { favoriteFoods.remove(food.getKey()); food.getFoodFans().remove(getKey()); }
Se un'istanza di Person
e un'istanza di
Food
contenute in Person.favoriteFoods
non si trovano nello stesso gruppo di entità e vuoi aggiornarle in un'unica transazione, devi impostare la configurazione JDO in modo da
abilitare le transazioni tra gruppi (XG).
Relazioni, gruppi di entità e transazioni
Quando l'applicazione salva un oggetto con relazioni di proprietà datastore, tutti gli altri oggetti che possono essere raggiunti tramite relazioni e che devono (sono nuovi o sono stati modificati dopo l'ultimo caricamento), vengono salvate automaticamente. Ciò ha importanti implicazioni per transazioni ed entità gruppi.
Considera l'esempio seguente in cui viene utilizzata una relazione unidirezionale tra
le classi Employee
e ContactInfo
riportate sopra:
Employee e = new Employee(); ContactInfo ci = new ContactInfo(); e.setContactInfo(ci); pm.makePersistent(e);
Quando il nuovo oggetto Employee
viene salvato utilizzando il metodo pm.makePersistent()
, il nuovo oggetto ContactInfo
correlato viene salvato automaticamente. Poiché entrambi gli oggetti sono
nuovo, App Engine crea due nuove entità nello stesso gruppo di entità,
utilizzando l'entità Employee
come padre di
Entità ContactInfo
. Analogamente, se l'oggetto Employee
è già stato salvato e l'oggetto ContactInfo
correlato è nuovo,
App Engine crea l'entità ContactInfo
utilizzando il modello
Entità Employee
come principale.
Nota, tuttavia, che la chiamata a pm.makePersistent()
in questo
nel caso in cui non venga utilizzata una transazione. Senza una transazione esplicita, entrambe le entità vengono create utilizzando azioni atomiche distinte. In questo caso,
possibili per la creazione dell'entità Employee, ma la creazione
l'entità ContactInfo in errore. Per assicurarti che entrambe le entità vengano create correttamente o che nessuna delle due venga creata, devi utilizzare una transazione:
Employee e = new Employee(); ContactInfo ci = new ContactInfo(); e.setContactInfo(ci); try { Transaction tx = pm.currentTransaction(); tx.begin(); pm.makePersistent(e); tx.commit(); } finally { if (tx.isActive()) { tx.rollback(); } }
Se entrambi gli oggetti sono stati salvati prima della relazione,
App Engine non può "spostare" l'entità ContactInfo
esistente nel
Gruppo di entità dell'entità Employee
, perché i gruppi di entità possono essere
assegnate al momento della creazione delle entità. App Engine può stabilire la relazione con un riferimento, ma le entità correlate non saranno nello stesso gruppo. In questo caso, le due entità possono essere aggiornate o eliminate nella stessa transazione se imposti la configurazione JDO in modo da abilitare le transazioni tra gruppi (XG). Se non utilizzi le transazioni XG, il tentativo di aggiornare o eliminare entità di gruppi diversi nella stessa transazione comporterà l'emissione di un'eccezione JDOFatalUser.
Se salvi un oggetto principale i cui oggetti secondari sono stati modificati, verranno salvate anche le modifiche apportate agli oggetti secondari. È buona norma consentire agli oggetti principali di mantenere la persistenza per tutti gli oggetti secondari correlati in questo modo e di utilizzare le transazioni quando si salvano le modifiche.
Elementi secondari dipendenti e Eliminazioni a cascata
Una relazione di proprietà può essere "dipendente", vale a dire che il figlio non può esistere
senza quello principale. Se una relazione è dipendente e un oggetto padre è
vengono eliminati, vengono eliminati anche tutti gli oggetti figlio. Rottura di una persona dipendente
di relazione assegnando un nuovo valore al campo dipendente sull'elemento padre anch'esso
il precedente. Puoi dichiarare una relazione uno a uno di proprietà come dipendente aggiungendo dependent="true"
all'annotazione Persistent
del campo nell'oggetto principale che fa riferimento a quello secondario:
// ... @Persistent(dependent = "true") private ContactInfo contactInfo;
Puoi dichiarare una relazione uno a molti di proprietà come dipendente aggiungendo
un'annotazione @Element(dependent = "true")
al campo dell'
oggetto principale che fa riferimento alla raccolta secondaria:
import javax.jdo.annotations.Element; // ... @Persistent @Element(dependent = "true") private ListcontactInfos;
Come per la creazione e l'aggiornamento degli oggetti, se vuoi che ogni eliminazione in un'eliminazione a cascata venga eseguita in un'unica azione atomica, devi eseguire l'eliminazione in una transazione.
Nota: l'implementazione JDO si occupa di eliminare oggetti secondari dipendenti, non il datastore. Se elimini un'entità principale utilizzando l'API di basso livello o la console Google Cloud, gli oggetti secondari correlati non verranno eliminati.
Relazioni polimorfiche
Anche se la specifica JDO include il supporto per
relazioni polimorfiche non sono ancora supportate in App Engine
FAI l'implementazione. Si tratta di una limitazione che speriamo di rimuovere nelle release future del prodotto. Se devi fare riferimento a più tipi di oggetti tramite una base comune
consigliamo la stessa strategia utilizzata per implementare
relazioni: archivia un riferimento chiave. Ad esempio, se disponi di una
Classe base Recipe
con Appetizer
, Entree
,
e le specializzazioni Dessert
, oltre a voler modellare il modello
Recipe
di Chef
, puoi modellarlo come segue:
Recipe.java
import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.Inheritance; import javax.jdo.annotations.InheritanceStrategy; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; @PersistenceCapable @Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE) public abstract class Recipe { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private int prepTime; }
Appetizer.java
// ... imports ... @PersistenceCapable public class Appetizer extends Recipe { // ... appetizer-specific fields }
Entree.java
// ... imports ... @PersistenceCapable public class Entree extends Recipe { // ... entree-specific fields }
Dessert.java
// ... imports ... @PersistenceCapable public class Dessert extends Recipe { // ... dessert-specific fields }
Chef.java
// ... imports ... @PersistenceCapable public class Chef { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent(dependent = "true") private Recipe favoriteRecipe; }
Purtroppo, se crei un'istanza di un Entree
e lo assegni a
Chef.favoriteRecipe
riceverai un
UnsupportedOperationException
quando provi a mantenere il
Chef
oggetto. perché il tipo di runtime dell'oggetto,
Entree
, non corrisponde al campo del tipo di relazione dichiarato,
Recipe
. Per risolvere il problema, cambia il tipo
Chef.favoriteRecipe
da Recipe
a
Key
:
Chef.java
// ... imports ... @PersistenceCapable public class Chef { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Key favoriteRecipe; }
Poiché Chef.favoriteRecipe
non è più un campo di relazione, può fare riferimento a un oggetto di qualsiasi tipo. Lo svantaggio è che, come con un modello non di proprietà
devi gestirla manualmente.