Como usar o JDO 3.0 com o App Engine

A Java Data Objects (JDO) é uma interface padrão para acessar bancos de dados em Java, fornecendo um mapeamento entre classes Java e tabelas de banco de dados. Há um plug-in de código aberto disponível para usar a JDO com o Datastore. Nesta página, fornecemos informações sobre como começar a usar esse plug-in.

Aviso: acreditamos que a maioria dos desenvolvedores terá uma experiência melhor usando a API Datastore de nível inferior ou uma das APIs de código aberto desenvolvidas especificamente para o Datastore, como a Objectify (em inglês). A JDO foi projetada para uso com bancos de dados relacionais tradicionais. Portanto, ela não pode representar explicitamente alguns dos aspectos do Datastore que a tornam diferente dos bancos de dados relacionais, como grupos de entidades e consultas de ancestral. Isso pode causar problemas sutis que são difíceis de entender e corrigir.

O SDK em Java do App Engine inclui a versão 2.x do plug-in DataNucleus para App Engine. Esse plug-in corresponde à versão 3.0 da DataNucleus Access Platform, que permite usar o App Engine Datastore por meio da JDO 3.0.

Consulte a documentação da Access Platform 3.0 para mais informações sobre o JDO. Em especial, confira Mapeamento do JDO e API JDO (todos em inglês).

Aviso: o plug-in do DataNucleus 2.x para o App Engine usa o DataNucleus v3.x. Esse novo plug-in não é totalmente retrocompatível com o anterior (1.x). Se você atualizar para a nova versão, não se esqueça de atualizar e testar seu aplicativo.

Criar ferramentas compatíveis com JDO 2.x e 3.0

Você pode usar o Apache Ant ou Maven para usar a versão 2.x ou 3.0 do plug-in DataNucleus para App Engine:

  • Para usuários do Ant: o SDK inclui uma tarefa Ant que executa a etapa de melhoria. Você precisa copiar os JARs e criar o arquivo de configuração ao definir seu projeto.
  • Para usuários do Maven: é possível melhorar as classes com as seguintes configurações no arquivo 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-core</artifactId>
                <version>3.1.3</version>
            </dependency>
        </dependencies>
    </plugin>

Como migrar para a versão 2.x do plug-in DataNucleus

Nesta seção, explicamos como fazer upgrade do app para que ele use a versão 2.x do plug-in DataNucleus para App Engine, que corresponde ao DataNucleus Access Platform 3.0 e JDO 3.0. Este plug-in não tem retrocompatibilidade total com o plug-in 1.x e está sujeito a mudanças. Se você fizer o upgrade, certifique-se de atualizar e testar o código de seu aplicativo.

Novos comportamentos padrão

A versão 2.x do plug-in DataNucleus do App Engine tem alguns padrões diferentes da versão anterior:

  • Chamadas não transacionais para PersistenceManager.makePersistent() e PersistenceManager.deletePersistent() agora são executadas de maneira atômica. Essas funções foram executadas anteriormente na próxima transação ou em PersistenceManager.close().
  • Agora não há mais uma exceção na alocação duplicada PersistenceManagerFactory (PMF). Em vez disso, se a propriedade de persistência datanucleus.singletonPMFForName estiver definida como verdadeiro, será retornado o singleton PMF atualmente alocado para esse nome.
  • Agora, os relacionamentos sem proprietário são compatíveis. Veja Relacionamentos sem proprietário.

Alterações nos arquivos de configuração

Para atualizar seu aplicativo para a versão 2.x do plug-in DataNucleus do App Engine, você precisa alterar as configurações em build.xml e jdoconfig.xml.

Aviso: depois de atualizar sua configuração, você precisa testar seu código de aplicativo para garantir a compatibilidade com versões anteriores. Se estiver configurando um novo aplicativo e quiser usar a versão mais recente do plug-in, consulte Como configurar a JDO 3.0.

  1. A propriedade PersistenceManagerFactoryClass foi alterada. Altere esta linha em jdoconfig.xml:

    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

    para:
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

Em build.xml

O destino de copyjars precisa mudar para acomodar o DataNucleus 2.x:

  1. O destino de copyjars mudou. Atualize esta seção:
    <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>
    para:
    <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>
  2. O destino de datanucleusenhance mudou. Atualize esta seção:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    para:
    <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>

Como configurar a JDO 3.0

Para usar a JDO para acessar o armazenamento de dados, um aplicativo do Google App Engine precisa disto:

  • Os JARs para JDO e o plug-in DataNucleus precisam estar no diretório war/WEB-INF/lib/ do aplicativo.
  • Um arquivo de configuração chamado jdoconfig.xml precisa estar no diretório war/WEB-INF/classes/META-INF/ do aplicativo, com a configuração que informa a JDO para usar o armazenamento de dados do App Engine.
  • O processo de criação do projeto precisa executar uma etapa de "melhoria" pós-compilação nas classes de dados compiladas para associá-las à implementação da JDO.

Como copiar os JARs

Os JARs da JDO e do armazenamento de dados estão incluídos no Java SDK do App Engine. Você pode encontrá-los no diretório appengine-java-sdk/lib/opt/user/datanucleus/v2/.

Copie os JARs para o diretório war/WEB-INF/lib/ do aplicativo.

Verifique se appengine-api.jar também está no diretório war/WEB-INF/lib/. Pode ser que você já tenha copiado esse arquivo ao criar o projeto. O plug-in DataNucleus do Google App Engine usa esse JAR para acessar o armazenamento de dados.

Como criar o arquivo jdoconfig.xml

A interface JDO precisa de um arquivo de configuração chamado jdoconfig.xml no diretório war/WEB-INF/classes/META-INF/ do aplicativo. Você pode criar esse arquivo nesse local diretamente ou copiar o arquivo de um diretório de origem durante o processo de criação.

Crie o arquivo com o seguinte conteúdo:

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
        <property name="datanucleus.appengine.singletonPMFForName" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

Como configurar a política de leitura do Datastore e o duração máxima de chamada

Conforme descrito na página Consultas do Datastore, você pode personalizar o comportamento do Datastore definindo a política de leitura (consistência forte versus eventual) e o duração máxima de chamada. Na JDO, faça isso especificando os valores desejados no elemento <persistence-manager-factory> do arquivo jdoconfig.xml. Todas as chamadas feitas com uma determinada instância de PersistenceManager usarão os valores de configuração em vigor quando o gerenciador for criado por PersistenceManagerFactory. Também é possível substituir essas configurações por um único objeto Query.

Para definir a política de leitura para um PersistenceManagerFactory, inclua uma propriedade chamada datanucleus.appengine.datastoreReadConsistency. Os valores possíveis são EVENTUAL e STRONG: se não for especificado, o padrão é STRONG. Observe, no entanto, que essas configurações se aplicam somente a consultas de ancestral dentro de um determinado grupo de entidades. As consultas não ancestrais são sempre consistentes, independentemente da política de leitura prevalecente.

<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

Você pode definir durações máximas da chamada ao armazenamento de dados separadas para leituras e gravações. Para leituras, use a propriedade padrão da JDO javax.jdo.option.DatastoreReadTimeoutMillis. Para gravações, use javax.jdo.option.DatastoreWriteTimeoutMillis. O valor é uma quantidade de tempo, em milissegundos.

<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
<property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

Se você quiser usar transações entre grupos (XG, na sigla em inglês), adicione a seguinte propriedade:

<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

Você pode ter vários elementos <persistence-manager-factory> no mesmo arquivo jdoconfig.xml, usando atributos name diferentes, para usar instâncias PersistenceManager com configurações distintas no mesmo aplicativo. Por exemplo, o arquivo jdoconfig.xml a seguir estabelece dois conjuntos de configurações, um chamado "transactions-optional" e outro chamado "eventual-reads-short-deadlines":

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>

    <persistence-manager-factory name="eventual-reads-short-deadlines">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>

        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
        <property name="datanucleus.singletonPMFForName" value="true" />
    </persistence-manager-factory>
</jdoconfig>

Para informações sobre como criar um PersistenceManager com um conjunto nomeado de configurações, consulte abaixo Como conseguir uma instância de PersistenceManager.

Como aprimorar classes de dados

A JDO usa uma etapa de "melhoria" pós-compilação no processo de criação para associar classes de dados à implementação da JDO.

Para realizar a etapa de aprimoramento em classes compiladas a partir da linha de comando, use o seguinte comando:

java -cp classpath com.google.appengine.tools.enhancer.Enhance
class-files

O classpath precisa conter o JAR appengine-tools-api.jar do diretório appengine-java-sdk/lib/ e todas as suas classes de dados.

Para mais informações sobre o otimizador de bytecode do DataNucleus, consulte a documentação do DataNucleus.

Como receber uma instância PersistenceManager

Um aplicativo interage com a JDO usando uma instância da classe PersistenceManager. Essa instância pode ser obtida ao instanciar e chamar um método em uma instância da classe PersistenceManagerFactory. A fábrica usa a configuração da JDO para criar instâncias de PersistenceManager.

Como uma instância de PersistenceManagerFactory demora para ser inicializada, um aplicativo deve reutilizar uma única instância. Uma maneira fácil de gerenciar a instância de PersistenceManagerFactory é criar uma classe wrapper singleton com uma instância estática, desta forma:

PMF.java

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

Dica: "transactions-optional" refere-se ao nome do conjunto de configurações no arquivo jdoconfig.xml. Caso seu aplicativo use vários conjuntos de configurações, estenda esse código para chamar JDOHelper.getPersistenceManagerFactory() conforme necessário. Seu código precisa armazenar em cache uma instância singleton de cada PersistenceManagerFactory.

O aplicativo usa a instância de fábrica para criar uma instância de PersistenceManager para cada solicitação que acessa o armazenamento de dados.

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import PMF;

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

O PersistenceManager é usado para armazenar, atualizar e excluir objetos de dados e para executar consultas do armazenamento de dados.

Ao terminar de usar a instância PersistenceManager, é necessário chamar o método close() dela. É errado usar a instância PersistenceManager depois de chamar o método close() dela.

try {
    // ... do stuff with pm ...
} finally {
    pm.close();
}

Recursos incompatíveis da JDO 3.0

A implementação do Google App Engine não oferece suporte aos seguintes recursos da interface JDO:

  • Relacionamentos próprios de muitos para muitos.
  • Consultas "join". Não é possível usar um campo de uma entidade filho em um filtro ao executar uma consulta sobre o tipo do pai. Observe que você pode testar o campo de relacionamento do pai diretamente em uma consulta usando uma chave.
  • Agrupamento JDOQL e outras consultas agregadas.
  • Consultas polimórficas. Não é possível realizar uma consulta de uma classe para receber instâncias de uma subclasse. Cada classe é representada por um tipo de entidade separado no armazenamento de dados.

Como desativar transações e portar aplicativos JDO existentes

A configuração da JDO que recomendamos define uma propriedade denominada datanucleus.appengine.autoCreateDatastoreTxns como true. Esta é uma propriedade específica do Google App Engine que instrui a implementação da JDO a associar transações do armazenamento de dados a transações JDO gerenciadas no código do aplicativo. Se você está criando um novo aplicativo desde o princípio, isso é recomendável. No entanto, se você tiver um aplicativo existente baseado em JDO que queira executar no App Engine, use uma configuração de persistência alternativa que defina o valor dessa propriedade como false:

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="false"/>
    </persistence-manager-factory>
</jdoconfig>

Para entender por que isso pode ser útil, lembre-se de que só é possível operar em objetos que pertencem ao mesmo grupo de entidades dentro de uma transação. Aplicativos criados usando bancos de dados tradicionais tipicamente assumem a disponibilidade de transações globais, que permitem que você atualize qualquer conjunto de registros dentro de uma transação. O armazenamento de dados do App Engine não oferece suporte a transações globais. Por isso, o App Engine gera exceções caso seu código assuma a disponibilidade de transações globais. Em vez de repassar sua base de código (potencialmente grande) e remover todo o código de gerenciamento de transações, você pode simplesmente desativar transações do armazenamento de dados. Isso não lida com os pressupostos feitos por seu código sobre a atomicidade de modificações em vários registros. Entretanto, fazer isso permite colocar seu aplicativo em funcionamento para que você possa se dedicar à refatoração incremental do código transacional, conforme necessário, em vez de fazer tudo ao mesmo tempo.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Java 8