Definizione delle classi di dati con JDO

Puoi utilizzare JDO per archiviare oggetti di dati Java semplici (a volte indicati come "Oggetti Java semplici" o "POJO") nel datastore. Ogni oggetto reso permanente con PersistenceManager diventa un'entità nel datastore. Puoi utilizzare le annotazioni per indicare a JDO come archiviare e ricreare le istanze delle tue classi di dati.

Nota:le versioni precedenti di JDO utilizzano file XML .jdo anziché annotazioni Java. Questi modelli funzionano ancora con JDO 2.3. Questa documentazione tratta solo l'utilizzo di annotazioni Java con classi di dati.

Annotazioni per classi e campi

Ogni oggetto salvato da JDO diventa un'entità nel datastore di App Engine. Il tipo di entità deriva dal nome semplice della classe (le classi interne utilizzano il percorso $ 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 (senza distinzione tra maiuscole e minuscole).

Per dichiarare una classe Java che può essere archiviata e recuperata dal datastore con JDO, assegna alla classe un'annotazione @PersistenceCapable. Ad esempio:

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Employee {
    // ...
}

I campi della classe di dati da archiviare nel datastore devono essere dichiarati come campi permanenti. Per dichiarare un campo come permanente, assegnagli un'annotazione @Persistent:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

Per dichiarare un campo come non permanente (non viene archiviato nel datastore e non viene ripristinato quando viene recuperato l'oggetto), assegnagli un'annotazione @NotPersistent.

Suggerimento:JDO specifica che i campi di alcuni tipi sono permanenti per impostazione predefinita se non vengono specificate né le annotazioni @Persistent@NotPersistent e i campi di tutti gli altri tipi non sono permanenti per impostazione predefinita. Per una descrizione completa di questo comportamento, consulta la documentazione di DataNucleus. Poiché non tutti i tipi di valori principali del datastore di App Engine sono permanenti per impostazione predefinita in base alla specifica JDO, consigliamo di annotare esplicitamente i campi come @Persistent o @NotPersistent per renderlo chiaro.

Il tipo di un campo può essere uno dei seguenti. Questi aspetti sono descritti in dettaglio di seguito.

  • uno dei tipi principali supportati dal datastore
  • Una raccolta (ad esempio java.util.List<...>) o un array di valori di un tipo di datastore principale
  • un'istanza o una raccolta di istanze di una classe @PersistenceCapable
  • un'istanza o una raccolta di istanze di una classe Serializable
  • Una classe incorporata, archiviata come proprietà nell'entità

Una classe di dati deve avere un solo 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 tipo più flessibile di campo chiave è un oggetto Key che viene completato automaticamente da JDO con un valore univoco in tutte le altre istanze della classe quando l'oggetto viene salvato nel datastore per la prima volta. Le chiavi primarie di tipo Key richiedono un'annotazione @PrimaryKey e un'annotazione @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY):

Suggerimento: imposta tutti i campi permanenti su private o protected (o protetti dal pacchetto) e fornisci l'accesso pubblico solo tramite i metodi della funzione di accesso. L'accesso diretto a un campo permanente da un'altra classe potrebbe ignorare il miglioramento della classe JDO. In alternativa, puoi creare altri corsi @PersistenceAware. Per ulteriori informazioni, consulta la documentazione di DataNucleus.

import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

Ecco un esempio di classe di dati:

import com.google.appengine.api.datastore.Key;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String firstName;

    @Persistent
    private String lastName;

    @Persistent
    private Date hireDate;

    public Employee(String firstName, String lastName, Date hireDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
    }

    // Accessors for the fields. JDO 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;
    }
}

Tipi di valori fondamentali

Per rappresentare una proprietà contenente un singolo valore di un tipo di core, dichiara un campo di tipo Java e utilizza l'annotazione @Persistent:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

Oggetti serializzabili

Un valore campo può contenere un'istanza di una classe Serializable, memorizzando il valore serializzato dell'istanza in un singolo valore di proprietà di tipo Blob. Per indicare a JDO di serializzare il valore, il campo utilizza l'annotazione @Persistent(serialized=true). I valori blob non sono indicizzati e non possono essere utilizzati nei filtri delle query o negli ordinamenti.

Ecco un esempio di una classe Serializable semplice che rappresenta un file, inclusi i contenuti, un nome e un tipo MIME. Non si tratta di una classe di dati JDO, quindi non sono presenti annotazioni di persistenza.

import java.io.Serializable;

public class DownloadableFile implements Serializable {
    private byte[] content;
    private String filename;
    private String mimeType;

    // ... accessors ...
}

Per archiviare un'istanza di una classe Serializable come valore Blob in una proprietà, dichiara un campo il cui tipo è la classe e utilizza l'annotazione @Persistent(serialized = "true"):

import javax.jdo.annotations.Persistent;
import DownloadableFile;

// ...
    @Persistent(serialized = "true")
    private DownloadableFile file;

Oggetti figlio e relazioni

Un valore di campo che è un'istanza di una classe @PersistenceCapable crea una relazione one-to-one di proprietà tra due oggetti. Un campo che è una raccolta di questi riferimenti crea una relazione one-to-many di proprietà.

Importante:le relazioni di proprietà hanno implicazioni per transazioni, gruppi di entità ed eliminazioni a cascata. Per ulteriori informazioni, consulta Transazioni e Relazioni.

Ecco un semplice esempio di una relazione one-to-one di proprietà tra un oggetto Employee e un oggetto 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;

    @Persistent
    private String city;

    @Persistent
    private String stateOrProvince;

    @Persistent
    private String zipCode;

    // ... accessors ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

In questo esempio, se l'app crea un'istanza Employee, compila il campo myContactInfo con una nuova istanza ContactInfo e poi salva l'istanza Employee con pm.makePersistent(...), il datastore crea due entità. Uno è del tipo "ContactInfo", che rappresenta l'istanza ContactInfo. L'altro è del tipo "Employee". La chiave dell'entità ContactInfo ha la chiave dell'entità Employee come padre del gruppo di entità.

Corsi incorporati

Le classi incorporate consentono di modellare un valore di campo utilizzando una classe senza creare una nuova entità di datastore né una relazione. I campi del valore dell'oggetto vengono archiviati direttamente nell'entità datastore per l'oggetto contenitore.

Qualsiasi classe di dati @PersistenceCapable può essere utilizzata come oggetto incorporato in un'altra classe di dati. I campi @Persistent della classe sono incorporati nell'oggetto. Se concedi alla classe di incorporare l'annotazione @EmbeddedOnly, questa potrà essere utilizzata solo come classe incorporata. La classe incorporata non ha bisogno di un campo chiave primaria perché non è archiviata come entità separata.

Ecco un esempio di classe incorporata. Questo esempio rende la classe incorporata una classe interna della classe di dati che la utilizza; ciò è utile, ma non necessario per rendere una classe incorporabile.

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...

@PersistenceCapable
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key key;
    @PersistenceCapable
    @EmbeddedOnly
    public static class ContactInfo {
        @Persistent
        private String streetAddress;

        @Persistent
        private String city;

        @Persistent
        private String stateOrProvince;

        @Persistent
        private String zipCode;

        // ... accessors ...
    }

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;
}

I campi di una classe incorporata vengono archiviati come proprietà nell'entità, utilizzando il nome di ciascun campo e quello della proprietà corrispondente. Se nell'oggetto sono presenti più campi il cui tipo è una classe incorporata, devi rinominare i campi di uno dei due in modo che non entrino in conflitto con un altro. Puoi specificare nuovi nomi di campi utilizzando argomenti per l'annotazione @Embedded. Ad esempio:

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;

    @Persistent
    @Embedded(members = {
        @Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
        @Persistent(name="city", columns=@Column(name="workCity")),
        @Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
        @Persistent(name="zipCode", columns=@Column(name="workZipCode")),
    })
    private ContactInfo workContactInfo;

Allo stesso modo, i campi dell'oggetto non devono utilizzare nomi che entrano in conflitto con i campi delle classi incorporate, a meno che i campi incorporati non vengano rinominati.

Poiché le proprietà permanenti della classe incorporata sono archiviate nella stessa entità degli altri campi, puoi utilizzare i campi permanenti della classe incorporata nei filtri delle query e negli ordinamenti JDOQL. Puoi fare riferimento al campo incorporato utilizzando il nome del campo esterno, un punto (.) e il nome del campo incorporato. Funziona indipendentemente dal fatto che i nomi delle proprietà per i campi incorporati siano stati modificati o meno utilizzando le annotazioni @Column.

    select from EmployeeContacts where workContactInfo.zipCode == "98105"

Raccolte

Una proprietà datastore può avere più di un valore. In JDO, questo è rappresentato da un singolo campo con un tipo di raccolta, dove la raccolta è uno dei tipi di valore principali o una classe Serializable. Sono supportati i seguenti tipi di raccolta:

  • java.util.ArrayList<...>
  • java.util.HashSet<...>
  • java.util.LinkedHashSet<...>
  • java.util.LinkedList<...>
  • java.util.List<...>
  • java.util.Map<...>
  • java.util.Set<...>
  • java.util.SortedSet<...>
  • java.util.Stack<...>
  • java.util.TreeSet<...>
  • java.util.Vector<...>

Se un campo viene dichiarato come Elenco, gli oggetti restituiti dal datastore avranno un valore ArrayList. Se un campo viene dichiarato come set, il datastore restituisce un HashSet. Se un campo viene dichiarato come SortedSet, il datastore restituisce un TreeSet.

Ad esempio, un campo di tipo List<String> viene archiviato come zero o più valori stringa per la proprietà, uno per ogni valore in List.

import java.util.List;
// ... imports ...

// ...
    @Persistent
    List<String> favoriteFoods;

Una raccolta di oggetti figlio (di @PersistenceCapable classi) crea più entità con una relazione one-to-many. Consulta Relazioni.

Le proprietà Datastore con più valori hanno un comportamento speciale per i filtri delle query e gli ordini di ordinamento. Per ulteriori informazioni, consulta la pagina Query sul datastore.

Campi oggetto e proprietà delle entità

Il datastore di App Engine distingue tra un'entità senza una determinata proprietà e un'entità con un valore null per una proprietà. JDO non supporta questa distinzione: ogni campo di un oggetto ha un valore, probabilmente null. Se un campo con un tipo di valore null (un tipo diverso da un tipo integrato come int o boolean) è impostato su null, quando l'oggetto viene salvato, la proprietà risultante verrà impostata con un valore nullo.

Se un'entità datastore viene caricata in un oggetto e non ha una proprietà per uno dei campi dell'oggetto e il tipo di campo è un tipo con valore singolo null, il campo viene impostato su null. Quando l'oggetto viene salvato nuovamente nel datastore, la proprietà null nel datastore viene impostata sul valore null. Se il campo non presenta un tipo di valore null, il caricamento di un'entità senza la proprietà corrispondente genera un'eccezione. Questo non si verifica se l'entità è stata creata dalla stessa classe JDO utilizzata per ricreare l'istanza, ma può verificarsi se la classe JDO cambia o se l'entità è stata creata utilizzando l'API di basso livello anziché JDO.

Se il tipo di un campo è una raccolta di un tipo di dati principali o una classe Serializable e non esistono valori per la proprietà nell'entità, la raccolta vuota viene rappresentata nel datastore impostando la proprietà su un singolo valore null. Se il campo è di tipo array, gli viene assegnato un array di 0 elementi. Se l'oggetto viene caricato e non esiste un valore per la proprietà, al campo viene assegnata una raccolta vuota del tipo appropriato. Internamente, il datastore conosce la differenza tra una raccolta vuota e una raccolta contenente un valore nullo.

Se l'entità ha una proprietà senza un campo corrispondente nell'oggetto, questa proprietà non è accessibile dall'oggetto. Se l'oggetto viene salvato nuovamente nel datastore, la proprietà aggiuntiva viene eliminata.

Se un'entità ha una proprietà il cui valore è di tipo diverso rispetto al campo corrispondente nell'oggetto, JDO tenta di trasmettere il valore al tipo di campo. Se il valore non può essere trasmesso al tipo di campo, JDO genera una ClassCastException. Nel caso di numeri (interi lunghi e valori a virgola mobile a doppia larghezza), il valore viene convertito, non trasmesso. Se il valore della proprietà numerica è superiore al tipo di campo, l'overflow della conversione viene generato senza generare un'eccezione.

Puoi dichiarare una proprietà non indicizzata aggiungendo la riga

    @Extension(vendorName="datanucleus", key="gae.unindexed", value="true")

sopra la proprietà nella definizione della classe. Consulta la sezione Proprietà non indicizzate nei documenti principali per ulteriori informazioni su cosa significa che una proprietà non viene indicizzata.

Ereditarietà

È naturale creare classi di dati che utilizzano l'ereditarietà e JDO supporta questa operazione. Prima di parlare del funzionamento dell'ereditarietà JDO su App Engine, ti consigliamo di leggere la documentazione di DataNucleus su questo argomento e di tornare indietro. Completato? Ok. L'ereditarietà JDO in App Engine funziona come descritto nella documentazione di DataNucleus con alcune limitazioni aggiuntive. Esamineremo queste restrizioni e faremo alcuni esempi concreti.

La strategia di ereditarietà con "nuova tabella" consente di suddividere i dati per un singolo oggetto dati in più "tabelle", ma poiché il datastore di 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à "nuova tabella" non è supportata per classi di dati che non sono alla base delle loro gerarchie di ereditarietà.

In secondo luogo, la strategia di ereditarietà "superclass-table" consente di archiviare i dati di un oggetto dati nella "tabella" della sua superclasse. Sebbene non esistano inefficienze intrinseche in questa strategia, al momento non è supportata. Potremmo rivederli nelle prossime versioni.

La buona notizia: le strategie "tabella-sottoclasse" e "tabella completa" funzionano come descritto nella documentazione di DataNucleus e puoi anche utilizzare "nuova-tabella" per qualsiasi oggetto dati che si trova alla radice della sua gerarchia di ereditarietà. Vediamo un esempio:

Worker.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 Worker {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String department;
}

Employee.java

// ... imports ...

@PersistenceCapable
public class Employee extends Worker {
    @Persistent
    private int salary;
}

Intern.java

import java.util.Date;
// ... imports ...

@PersistenceCapable
public class Intern extends Worker {
    @Persistent
    private Date internshipEndDate;
}

In questo esempio abbiamo aggiunto un'annotazione @Inheritance alla dichiarazione della classe Worker con l'attributo strategy> impostato su InheritanceStrategy.SUBCLASS_TABLE. Questo indica a JDO di archiviare tutti i campi permanenti di Worker nelle entità datastore delle sue sottoclassi. L'entità datastore creata come risultato della chiamata a makePersistent() con un'istanza Employee ha due proprietà denominate "department" e "salary". L'entità datastore creata come risultato della chiamata a makePersistent() con un'istanza Intern avrà due proprietà denominate "department" e "internshipEndDate". Il datastore non contiene 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;
// ... imports ...

@PersistenceCapable
@Inheritance(customStrategy = "complete-table")
public class FormerEmployee extends Employee {
    @Persistent
    private Date lastDay;
}

In questo esempio, abbiamo aggiunto un'annotazione @Inheritance alla dichiarazione della classe FormerEmployee con l'attributo custom-strategy> impostato su "complete-table". Questo indica a JDO di archiviare tutti i campi permanenti di FormerEmployee e delle sue superclassi in entità datastore corrispondenti alle istanze FormerEmployee. L'entità datastore creata come risultato della chiamata a makePersistent() con un'istanza FormerEmployee avrà tre proprietà denominate "department", "salary" e "lastDay". Nessuna entità di tipo "Dipendente" corrisponde a FormerEmployee. Tuttavia, se chiami makePersistent() con un oggetto il cui tipo di runtime è Employee, crei un'entità di tipo "Dipendente".

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 saperne di più, consulta la sezione Relazioni polimorfiche.