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 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
É possível usar o Maven para usar as versões 2.x ou 3.0 do plug-in DataNucleus para App Engine:
- Para usuários do Maven: você pode aprimorar as classes com as seguintes configurações no seu 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()
ePersistenceManager.deletePersistent()
agora são executadas atomicamente. Essas funções foram executadas anteriormente na próxima transação ou emPersistenceManager.close()
. - Agora não há mais uma exceção na alocação duplicada PersistenceManagerFactory (PMF). Em vez disso, se você tiver a propriedade de persistência
datanucleus.singletonPMFForName
definida como true, ela retornará o PMF singleton 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 definições de configuração no build.xml
e no jdoconfig.xml
.
Atenção! 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.
- A propriedade
PersistenceManagerFactoryClass
foi alterada. Alterar esta linha emjdoconfig.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"/>
No build.xml
O destino copyjars
precisa ser alterado para acomodar o DataNucleus 2.x:
- O destino
copyjars
foi alterado. Atualize esta seção:
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="**/*.jar" /> </fileset> </copy> </target>
<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>
- O destino
datanucleusenhance
foi alterado. Atualize esta seção:
para:<target name="datanucleusenhance" depends="compile" description="Performs enhancement on compiled data classes."> <enhance_war war="war" /> </target>
<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 denominado
jdoconfig.xml
precisa estar no diretóriowar/WEB-INF/classes/META-INF/
do app, com configuração que instrui a JDO a usar o armazenamento de dados do App Engine; - o processo de criação do projeto execute uma etapa de “aprimoramento” pós-compilação nas classes de dados compiladas para associá-las à implementação da JDO.
Como copiar os JARs
A JDO e os JARs do armazenamento de dados estão incluídos no SDK para Java do Google App Engine. Eles podem ser encontrados
no diretório appengine-java-sdk/lib/opt/user/datanucleus/v2/
.
Copie os JARs para o diretório war/WEB-INF/lib/
do seu aplicativo.
Certifique-se de que appengine-api.jar
também esteja 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 denominado 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 a duração máxima de chamada. Na JDO, você faz isso especificando os valores pretendidos no elemento <persistence-manager-factory>
do arquivo jdoconfig.xml
. Todas as chamadas feitas com uma determinada instância do PersistenceManager
usarão os valores de configuração efetivos quando o gerenciador for criado por PersistenceManagerFactory
. Também é possível modificar essas configurações para um único objeto Query
.
Para definir a política de leitura para um PersistenceManagerFactory
, inclua uma propriedade denominada datanucleus.appengine.datastoreReadConsistency
.
Os valores possíveis são EVENTUAL
e STRONG
: se não for especificado, o padrão será 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" />
É possível pode ter vários elementos <persistence-manager-factory>
no mesmo arquivo jdoconfig.xml
, usando atributos name
diferentes, para usar instâncias de PersistenceManager
com configurações diferentes no mesmo app. Por exemplo, o seguinte arquivo jdoconfig.xml
estabelece dois conjuntos de configuração, um denominado "transactions-optional"
e outro denominado "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>
Consulte Como receber uma instância do PersistenceManager abaixo para informações sobre como criar um PersistenceManager
com um conjunto de configuração específico.
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/
, bem como todas as classes de dados.
Para mais informações sobre o otimizador de bytecode do DataNucleus, consulte a documentação do DataNucleus (em inglês).
Como receber uma instância PersistenceManager
Um aplicativo interage com a JDO usando uma instância da classe PersistenceManager. Essa instância pode ser recebida 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"
se refere ao nome do conjunto de configuração no arquivo jdoconfig.xml
. Caso seu aplicativo use vários conjuntos de configuração, você precisará estender esse código para chamar JDOHelper.getPersistenceManagerFactory()
, conforme quiser. 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 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. É um erro usar a instância do PersistenceManager depois de chamar o método close()
correspondente.
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 filha 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. Entretanto, se houver um aplicativo baseado em JDO que você queira executar no App Engine, talvez convenha usar uma configuração de permanência alternativa que define 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 trata dos pressupostos feitos por seu código sobre a atomicidade de modificações em vários registros. Entretanto, é possível colocar seu aplicativo em funcionamento para você se dedicar à refatoração incremental do código transacional, conforme necessário, em vez de fazer tudo ao mesmo tempo.