My favorites | Português | Sign in

Como definir classes de dados

Você pode usar a JDO para armazenar objetos de dados Java simples (algumas vezes chamados de "POJOs", "Plain Old Java Objects") no armazenamento de dados. Cada objeto tornado persistente com o PersistenceManager se torna uma entidade no armazenamento de dados. Você usa anotações para informar à JDO como armazenar e recriar instâncias das suas classes de dados.

Observação: As versões anteriores da JDO usam arquivos XML .jdo em vez de anotações em Java. Eles ainda funcionam com a JDO 2.3. Esta documentação aborda apenas o uso das anotações em Java com as classes de dados.

Anotações de classe e campo

Cada objeto salvo pela JDO se torna uma entidade no armazenamento de dados do Google App Engine. O tipo da entidade deriva do nome simples da classe (classes internas usam o caminho $ sem o nome do pacote). Cada campo persistente da classe representa uma propriedade da entidade, com o nome da propriedade igual ao nome do campo (com as letras maiúsculas e minúsculas preservadas).

Para declarar uma classe Java como capaz de ser armazenada e recuperada do armazenamento de dados com JDO, forneça à classe uma anotação @PersistenceCapable. Por exemplo:

import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    // ...
}

Os campos da classe de dados que deverão ser armazenados no armazenamento de dados devem ser declarados como campos persistentes. Para declarar um campo como persistente, forneça a ele uma anotação @Persistent:

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

// ...
    @Persistent
    private Date hireDate;

Para declarar um campo como não persistente (ele não é armazenado no armazenamento de dados e não é restaurado quando o objeto é recuperado), forneça a ele uma anotação @NotPersistent.

Dica: A JDO especifica que os campos de determinados tipos serão persistentes por padrão se as anotações @Persistent ou @NotPersistent não forem especificadas, e os campos de todos os outros tipos não forem persistentes por padrão. Consulte a documentação do DataNucleus para obter uma descrição completa desse comportamento. Como nem todos os principais tipos de valor do armazenamento de dados do Google App Engine são persistentes por padrão de acordo com a especificação JDO, recomendamos a anotação explícita dos campos como @Persistent ou @NotPersistent para ficar claro.

O tipo de um campo pode ser um dos apresentados a seguir. Eles estão descritos mais detalhadamente abaixo.

  • um dos principais tipos suportados pelo armazenamento de dados
  • uma coleção (como um java.util.List<...>) ou uma matriz de valores de um tipo de armazenamento de dados principal
  • uma instância ou coleção de instâncias de uma classe @PersistenceCapable
  • uma instância ou coleção de instâncias de uma classe Serializable
  • uma classe incorporada, armazenada como propriedades na entidade

Uma classe de dados precisa ter um campo dedicado ao armazenamento da chave principal da entidade do armazenamento de dados correspondente. Você pode escolher entre 4 tipos diferentes de campos de chave, cada um usando um tipo de valor e anotações diferentes (consulte Como criar dados: chaves para obter mais informações). O campo de chave mais simples é um valor inteiro long preenchido automaticamente pela JDO com um valor exclusivo em todas as outras instâncias da classe quando o objeto é salvo no armazenamento de dados pela primeira vez. As chaves com valores inteiros long usam as anotações @PrimaryKey e @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY):

Dica: Torne todos os seus campos persistentes private ou protected (ou protegidos por pacote) e forneça apenas acesso público por meio dos métodos de acesso. O acesso direto a um campo persistente de outra classe pode ignorar o aprimoramento da classe JDO. Se preferir, você pode tornar outras classes @PersistenceAware. Consulte a documentação do DataNucleus para obter mais informações.

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

Veja um exemplo de classe de dados:

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @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 Long getId() {
        return id;
    }

    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 String getHireDate() {
        return hireDate;
    } 
    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    } 
}

Principais tipos de valor

O armazenamento de dados suporta os principais tipos de valor abaixo:

Tipo Classe Java Ordem de classificação Observações
string de texto curta, < 500 bytes java.lang.String Unicode Um valor maior do que 500 bytes gera uma JDOFatalUserException.
string de byte curta, < 500 bytes com.google.appengine.api.datastore.ShortBlob ordem de bytes Um valor maior do que 500 bytes gera uma JDOFatalUserException.
Valor booleano boolean ou java.lang.Boolean false < true
inteiro short, java.lang.Short, int, java.lang.Integer, long, java.lang.Long Numérico Armazenado como um valor inteiro long e convertido no tipo de campo. Estouro de valores fora do intervalo.
número de ponto flutuante float, java.lang.Float, double, java.lang.Double Numérico Armazenado como um ponto flutuante de dupla precisão e convertido no tipo de campo. Estouro de valores fora do intervalo.
data-hora java.util.Date Cronológica
Conta do Google com.google.appengine.api.users.User Por endereço de e-mail (Unicode)
string de texto longa com.google.appengine.api.datastore.Text (não classificável) Não indexado
string de byte longa com.google.appengine.api.datastore.Blob (não classificável) Não indexado
chave de entidade com.google.appengine.api.datastore.Key ou o objeto referenciado (como um filho) Por elementos de caminho (tipo, ID ou nome, tipo, ID ou nome...)
um URL com.google.appengine.api.datastore.Link Unicode

Observação: O armazenamento de dados suporta diversos tipos de valores principais adicionais que ainda não foram implementados na API em Java do armazenamento de dados, mas foram implementados na API em Python. Se uma propriedade tiver um valor com qualquer um desses tipos e a entidade for carregada em um objeto com um campo da propriedade, a JDO se comportará como se a propriedade não existisse. Ela definirá o campo como null ou gerará uma NullPointerException se o tipo de campo não puder ser anulado. Os tipos não suportados são: Category, Email, IM, PhoneNumber, PostalAddress, Rating, GeoPt.

Para representar uma propriedade contendo um único valor de um tipo principal, declare um campo do tipo Java e use a anotação @Persistent:

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

// ...
    @Persistent
    private Date hireDate;

Objetos serializáveis

O valor de um campo pode conter uma instância de uma classe Serializable, que armazena o valor serializado da instância em um único valor de propriedade do tipo Blob. Para instruir a JDO a serializar o valor, o campo usa a anotação @Persistent(serialized=true). Os valores Blob não são indexados e não podem ser usados em filtros de consulta ou ordens de classificação.

Veja um exemplo de uma classe Serializable simples que representa um arquivo, incluindo o conteúdo do arquivo, um nome de arquivo e um tipo MIME. Isso não é uma classe de dados JDO, por isso não há anotações de persistência.

import java.io.Serializable;

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

    // ... accessors ...
}

Para armazenar uma instância de uma classe Serializable como um valor Blob em uma propriedade, declare um campo cujo tipo seja a classe e use a anotação @Persistent(serialized = "true"):

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

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

Objetos filho e relacionamentos

O valor de um campo que é uma instância de uma classe @PersistenceCapable cria um relacionamento proprietário de um-para-um entre dois objetos. Um campo que é uma coleção dessas referências cria um relacionamento proprietário de um-para-vários.

Importante: Relacionamentos proprietários têm implicações para transações, grupos de entidades e exclusões em cascata. Consulte Transações e Relacionamentos para obter mais informações.

Veja um exemplo simples de um relacionamento proprietário de um-para-um entre um objeto Employee e um objeto ContactInfo:

ContactInfo.java

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
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(identityType = IdentityType.APPLICATION)
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

Nesse exemplo, se o aplicativo criar uma instância Employee, preencher o seu campo myContactInfo com uma nova instância ContactInfo e salvar a instância Employee com pm.makePersistent(...), o armazenamento de dados criará duas entidades. Uma é do tipo "ContactInfo", representando a instância ContactInfo. A outra é do tipo "Employee". A chave da entidade ContactInfo tem a chave da entidade Employee como pai do seu grupo de entidades.

Classes incorporadas

As classes incorporadas permitem que você modele um valor de campo usando uma classe sem criar uma nova entidade de armazenamento de dados e sem formar um relacionamento. Os campos do valor do objeto são armazenados diretamente na entidade do armazenamento de dados do objeto que contém.

Qualquer classe de dados @PersistenceCapable pode ser usada como um objeto incorporado em outra classe de dados. Os campos @Persistent da classe são incorporados no objeto. Se você permitir que a classe incorpore a anotação @EmbeddedOnly, a classe poderá ser usada apenas como uma classe incorporada. A classe incorporada não precisa de um campo de chave principal, pois não é armazenada como uma entidade separada.

Veja um exemplo de uma classe incorporada. Esse exemplo torna a classe incorporada uma classe interna da classe de dados que a utiliza; isso é útil, mas não é necessário para tornar uma classe incorporável.

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Long id;
    
    @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;
}

Os campos de uma classe incorporada são armazenados como propriedades na entidade, usando o nome de cada campo e o nome da propriedade correspondente. Se você tiver mais de um campo no objeto cujo tipo seja uma classe incorporada, precisará renomear os campos de um para que não entrem em conflito com o do outro. Especifique novos nomes de campo usando argumentos para a anotação @Embedded. Por exemplo:

    @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;

Da mesma forma, os campos no objeto não devem usar nomes que entrem em conflito com os campos das classes incorporadas, a menos que os campos incorporados sejam renomeados.

Como as propriedades persistentes da classe incorporada são armazenadas na mesma entidade que os outros campos, você pode usar os campos persistentes da classe incorporada nos filtros de consulta e nas ordens de classificação JDOQL. Você pode consultar o campo incorporado usando o nome do campo externo, um ponto (.), e o nome do campo incorporado. Isso funciona independentemente de os nomes de propriedade dos campos incorporados terem sido ou não alterados usando as anotações @Column.

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

Coleções

A propriedade de um armazenamento de dados pode ter mais de um valor. Na JDO, isso é representado por um único campo com um tipo Collection, em que a coleção é um dos principais tipos de valor ou uma classe Serializable. São suportados os seguintes tipos Collection:

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

Se um campo for declarado como uma List, os objetos retornados pelo armazenamento de dados terão um valor ArrayList. Se um campo for declarado como um Set, o armazenamento de dados retornará um HashSet. Se um campo for declarado como um SortedSet, o armazenamento de dados retornará um TreeSet.

Por exemplo, um campo cujo tipo seja List<String> é armazenado como nenhum ou mais de um valor de string para a propriedade, um para cada valor em List.

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

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

Uma coleção de objetos filho (de classes @PersistenceCapable) cria diversas entidades com um relacionamento de-um-para-vários. Consulte Relacionamentos.

As propriedades de armazenamento de dados com mais de um valor têm um comportamento especial para os filtros de consulta e ordens de classificação. Consulte Consultas e índices: ordens de classificação e propriedades com diversos valores para obter mais informações.

Campos de objeto e propriedades de entidades

O armazenamento de dados do Google App Engine faz distinção entre uma entidade sem uma determinada propriedade e uma entidade com um valor null para uma propriedade. A JDO não suporta essa distinção: todo campo de um objeto tem um valor, possivelmente null. Se um campo com um tipo de valor anulável (algo diferente de um tipo integrado como int ou boolean) for definido como null, quando o objeto for salvo, a entidade resultante terá a propriedade definida com um valor nulo.

Se a entidade de um armazenamento de dados for carregada em um objeto e não tiver uma propriedade para um dos campos do objeto e o tipo do campo for um tipo de valor único anulável, o campo será definido como null. Quando o objeto é salvo novamente no armazenamento de dados, a propriedade null é definida no armazenamento de dados como o valor nulo. Se o campo não for de um tipo de valor anulável, carregar uma entidade sem a propriedade correspondente gerará uma exceção. Isso não acontecerá se a entidade tiver sido criada a partir da mesma classe JDO usada para recriar a instância, mas poderá ocorrer se a classe JDO mudar ou se a entidade tiver sido criada usando a API de nível inferior em vez de JDO.

Se o tipo de um campo for uma Collection de um tipo principal de dados ou de uma classe Serializable e não houver valores para a propriedade na entidade, a coleção vazia será representada no armazenamento de dados por meio da configuração da propriedade como um valor nulo único. Se o tipo do campo for um tipo de matriz, ele receberá uma matriz sem elementos. Se o objeto for carregado e não houver valor para a propriedade, o campo receberá uma coleção vazia do tipo apropriado. Internamente, o armazenamento de dados sabe a diferença entre uma coleção vazia e uma coleção que contém um valor nulo.

Se a entidade tiver uma propriedade que não tenha um campo correspondente no objeto, essa propriedade não poderá ser acessada a partir do objeto. Se o objeto for salvo novamente no armazenamento de dados, a propriedade extra será excluída.

Se uma entidade tiver uma propriedade cujo valor seja de um tipo diferente do campo correspondente no objeto, a JDO tentará lançar o valor no tipo de campo. Se o valor não puder ser lançado no tipo de campo, a JDO gerará uma ClassCastException. No caso dos números (inteiros long e pontos flutuantes de dupla precisão), o valor será convertido, não lançado. Se o valor da propriedade numérica for maior do que o tipo de campo, ocorrerá um estouro da conversão sem gerar uma exceção.