Criar, obter e eliminar dados no JDO

Guardar um objeto de dados JDO no arquivo de dados é uma questão simples de chamar o método makePersistent() da instância PersistenceManager. A implementação do JDO do App Engine usa o campo da chave principal do objeto para controlar a entidade da base de dados que corresponde ao objeto de dados e pode gerar chaves para novos objetos automaticamente. Pode usar chaves para obter entidades rapidamente e pode criar chaves a partir de valores conhecidos (como IDs de contas).

Tornar os objetos persistentes

Para armazenar um objeto de dados simples no arquivo de dados, chama o método makePersistent() do PersistenceManager, transmitindo-lhe a instância.

PersistenceManager pm = PMF.get().getPersistenceManager();

Employee e = new Employee("Alfred", "Smith", new Date());

try {
    pm.makePersistent(e);
} finally {
    pm.close();
}

A chamada para makePersistent() é síncrona e não é devolvida até o objeto ser guardado e os índices serem atualizados.

Para guardar vários objetos no JDO, chame o método makePersistentAll(...) com uma coleção de objetos. Este método usa uma única operação de gravação em lote de baixo nível que é mais eficiente do que uma série de invocações makePersistent(...) individuais.

Nota: se algum dos campos persistentes do objeto de dados for uma referência a outros objetos de dados persistentes e algum desses objetos nunca tiver sido guardado ou tiver sido alterado desde que foi carregado, os objetos referenciados também são guardados no armazenamento de dados. Consulte Relações.

Chaves

Cada entidade tem uma chave que é exclusiva para todas as entidades no App Engine. Uma chave completa inclui várias informações, incluindo o ID da aplicação, o tipo e um ID da entidade. (As chaves também contêm informações sobre grupos de entidades; consulte Transações para mais informações.)

A chave de um objeto é armazenada num campo na instância. Identifica o campo da chave primária através da anotação @PrimaryKey.

A app pode fornecer a parte do ID da chave como uma string quando o objeto é criado ou pode permitir que o repositório de dados gere automaticamente um ID numérico. A chave completa tem de ser exclusiva em todas as entidades no arquivo de dados. Por outras palavras, um objeto tem de ter um ID exclusivo em todos os objetos do mesmo tipo e com o mesmo grupo de entidades principal (se existir). Seleciona o comportamento pretendido da chave através do tipo do campo e das anotações.

Se a classe for usada como uma classe "secundária" numa relação, o campo de chave tem de ser de um tipo capaz de representar um principal do grupo de entidades: uma instância de chave ou um valor de chave codificado como uma string. Consulte Transações para mais informações sobre grupos de entidades e Relações para mais informações sobre relações.

Dica: se a app criar um novo objeto e lhe atribuir o mesmo ID de string que outro objeto do mesmo tipo (e o mesmo grupo de entidades principal), a gravação do novo objeto substitui o outro objeto no arquivo de dados. Para detetar se um ID de string já está a ser usado antes de criar um novo objeto, pode usar uma transação para tentar obter uma entidade com um determinado ID e, em seguida, criar uma se não existir. Consulte Transações.

Existem quatro tipos de campos de chave primária:

Longo

Um número inteiro longo (java.lang.Long), um ID de entidade gerado automaticamente pelo arquivo de dados. Use esta opção para objetos sem itens superiores do grupo de entidades cujos IDs devem ser gerados automaticamente pela base de dados. O campo de chave longa de uma instância é preenchido quando a instância é guardada.

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
String não codificada

Uma string (java.lang.String), um ID da entidade ("nome da chave") fornecido pela aplicação quando o objeto é criado. Use isto para objetos sem itens principais do grupo de entidades cujos IDs devem ser fornecidos pela aplicação. A aplicação define este campo para o ID pretendido antes de guardar.

import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    private String name;
Chave

Uma instância de chave (com.google.appengine.api.datastore.Key). O valor da chave inclui a chave do elemento principal do grupo de entidades (se existir) e o ID de string atribuído pela app ou o ID numérico gerado pelo sistema. Para criar o objeto com um ID de string atribuído pela app, cria o valor da chave com o ID e define o campo para o valor. Para criar o objeto com um ID numérico atribuído pelo sistema, deixe o campo da chave nulo. (Para obter informações sobre como usar os itens principais do grupo de entidades, consulte Transações.)

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;

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

    public void setKey(Key key) {
        this.key = key;
    }

A app pode criar uma instância Key através da classe KeyFactory:

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

// ...
        Key key = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = new Employee();
        e.setKey(key);
        pm.makePersistent(e);
Chave como string codificada

Semelhante a Key, mas o valor é a forma de string codificada da chave. As chaves de strings codificadas permitem-lhe escrever a sua aplicação de forma portátil e continuar a tirar partido dos grupos de entidades do Datastore do App Engine.

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

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

A app pode preencher este valor antes de guardar através de uma chave com um nome ou pode deixá-lo nulo. Se o campo de chave codificado for nulo, o campo é preenchido com uma chave gerada pelo sistema quando o objeto é guardado.

As instâncias de chaves podem ser convertidas para e a partir da representação de string codificada através dos métodos KeyFactory keyToString() e stringToKey().

Quando usa strings de chaves codificadas, pode conceder acesso à string ou ao ID numérico de um objeto com um campo adicional:

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
    private String keyName;

    // OR:

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-id", value="true")
    private Long keyId;

Um campo "gae.pk-name" pode ser definido para um nome de chave antes de guardar o objeto. Quando o objeto é guardado, o campo da chave codificada é preenchido com a chave completa que inclui o nome da chave. O tipo tem de ser String.

Um campo "gae.pk-id" é preenchido quando o objeto é guardado e não pode ser modificado. O respetivo tipo tem de ser Long.

Quando é criado um novo objeto com uma chave gerada (um campo de chave que usa valueStrategy = IdGeneratorStrategy.IDENTITY), o respetivo valor principal começa com null. O campo de chave é preenchido quando o objeto é escrito no repositório de dados. Se estiver a usar uma transação, o objeto é escrito quando a transação é confirmada. Caso contrário, o objeto é escrito quando o método makePersistent() é chamado se o objeto estiver a ser criado ou quando o método close() da instância PersistenceManager é chamado se o objeto estiver a ser atualizado.

Para mais informações sobre como criar chaves, consulte o artigo Entidades, propriedades e chaves.

Obter um objeto por chave

Para obter um objeto com base na respetiva chave, use o método getObjectById() do PersistenceManager. O método recebe a classe do objeto e a chave:

        Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = pm.getObjectById(Employee.class, k);

Se a classe usar um campo de chave que seja um ID de string não codificado (String) ou um ID numérico (Long), getObjectByID() pode usar o valor simples como o parâmetro de chave:

        Employee e = pm.getObjectById(Employee.class, "Alfred.Smith@example.com");

O argumento key pode ser de qualquer um dos tipos de campos de chaves suportados (ID de string, ID numérico, valor da chave, string da chave codificada) e pode ser de um tipo diferente do campo de chave na classe. O App Engine tem de conseguir obter a chave completa a partir do nome da classe e do valor fornecido. Os IDs de string e os IDs numéricos são exclusivos, pelo que uma chamada que use um ID numérico nunca devolve uma entidade com um ID de string. Se for usado um valor de chave ou uma string de chave codificada, a chave tem de se referir a uma entidade cujo tipo é representado pela classe.

Atualizar um objeto

Uma forma de atualizar um objeto com JDO é obter o objeto e, em seguida, modificá-lo enquanto o PersistenceManager que devolveu o objeto ainda está aberto. As alterações são mantidas quando o PersistenceManager é fechado. Por exemplo:

public void updateEmployeeTitle(User user, String newTitle) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try {
        Employee e = pm.getObjectById(Employee.class, user.getEmail());
        if (titleChangeIsAuthorized(e, newTitle) {
            e.setTitle(newTitle);
        } else {
            throw new UnauthorizedTitleChangeException(e, newTitle);
        }
    } finally {
        pm.close();
    }
}

Uma vez que a instância Employee foi devolvida pelo PersistenceManager, o PersistenceManager tem conhecimento de todas as modificações feitas aos campos persistentes no Employee e atualiza automaticamente o arquivo de dados com estas modificações quando o PersistenceManager é fechado. Sabe isto porque a instância Employee está "associada" ao PersistenceManager.

Pode modificar um objeto depois de o PersistenceManager ter sido fechado declarando a classe como "detachable" (separável). Para tal, adicione o atributo detachable à anotação @PersistenceCapable:

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(detachable="true")
public class Employee {
    // ...
}

Agora, pode ler e escrever os campos de um objeto Employee depois de o PersistenceManager que o carregou ter sido fechado. O exemplo seguinte ilustra como um objeto desanexado pode ser útil:

public Employee getEmployee(User user) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Employee employee, detached = null;
    try {
        employee = pm.getObjectById(Employee.class,
            "Alfred.Smith@example.com");

        // If you're using transactions, you can call
        // pm.setDetachAllOnCommit(true) before committing to automatically
        // detach all objects without calls to detachCopy or detachCopyAll.
        detached = pm.detachCopy(employee);
    } finally {
        pm.close();
    }
    return detached;
}

public void updateEmployeeTitle(Employee e, String newTitle) {
    if (titleChangeIsAuthorized(e, newTitle) {
        e.setTitle(newTitle);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(e);
        } finally {
            pm.close();
        }
    } else {
        throw new UnauthorizedTitleChangeException(e, newTitle);
    }
}

Os objetos separados são uma boa alternativa à criação de objetos de transferência de dados. Para mais informações sobre como trabalhar com objetos separados, consulte a documentação do DataNucleus.

Eliminar um objeto

Para eliminar um objeto do arquivo de dados, chame o método deletePersistent() do PersistenceManager com o objeto:

pm.deletePersistent(e);

Para eliminar vários objetos no JDO, chame o método deletePersistentAll(...) com uma coleção de objetos. Este método usa uma única operação de eliminação em lote de baixo nível que é mais eficiente do que uma série de invocações deletePersistent(...) individuais.

Se um objeto tiver campos que contenham objetos secundários que também sejam persistentes, os objetos secundários também são eliminados. Consulte a secção Relações para mais informações.

Para eliminar todos os objetos que correspondam a uma consulta, pode usar a funcionalidade "eliminar por consulta" do JDOQL. Consulte o artigo Eliminar entidades por consulta para mais informações.