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 Java do App Engine inclui uma implementação da JDO 2.3 para o App Engine Datastore. A implementação é baseada na versão 1.0 da DataNucleus Access Platform, a implementação de referência de código aberto para JDO 2.3.
Observação: as instruções nesta página referem-se à JDO versão 2.3, que usa a versão 1.0 do plug-in DataNucleus para App Engine. O App Engine agora oferece o plug-in DataNucleus 2.x, que permite executar a JDO 3.0. O novo plug-in aceita relacionamentos sem proprietário e fornece uma série de novos recursos e APIs. Esse upgrade não é totalmente compatível com a versão anterior. Se você recriar um aplicativo usando a JDO 3.0, precisará atualizar e testar novamente o código. Para mais informações sobre a nova versão, consulte JDO 3.0 (link em inglês). Para mais informações sobre upgrade, consulte Como usar a JDO 3.0 com o App Engine.
Como configurar a JDO 2.3
Para usar a JDO com o objetivo de acessar o armazenamento de dados, um aplicativo do App Engine precisa que:
- os JARs da JDO e do plug-in DataNucleus do App Engine estejam no diretório
war/WEB-INF/lib/
do app; - 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. Você os encontra no diretório appengine-java-sdk/lib/user/orm/
.
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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> <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> </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 que essas configurações se referem somente a consultas de ancestral em um determinado grupo de entidades.
As consultas entre grupos e que não são de ancestral sempre têm consistência eventual, independentemente da política de leitura que prevalece.
<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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> <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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> <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" /> </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 precisa reutilizar uma única instância. Para impor esse comportamento, uma exceção é gerada quando o aplicativo instancia mais de uma PersistenceManagerFactory (com o mesmo nome de configuração). Uma maneira fácil de gerenciar a instância de PersistenceManagerFactory é criar uma classe wrapper de 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 2.3
A implementação do App Engine é incompatível com os seguintes recursos da interface JDO:
- Relacionamentos sem proprietário. Você pode implementar relacionamentos sem proprietário usando valores explícitos de Key. A sintaxe da JDO para relacionamentos sem proprietário poderá ser aceita em uma versão futura.
- Relacionamentos com proprietário 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.
IdentityType.DATASTORE
para a anotação@PersistenceCapable
. SomenteIdentityType.APPLICATION
é aceito.- Atualmente há um bug que impede relacionamentos de um para muitos em que o pai e o filho são da mesma classe, dificultando a modelagem de estruturas de árvore. Isso será corrigido em uma versão futura. Você pode contornar esse problema armazenando valores explícitos de Key para o pai ou para os filhos.
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.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> <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.